I have been trying to get Angular animations working for child components (Content Children) with a stagger for several days now but despite reading everything I can find I cannot get it tow work.
This question appears to be doing roughly what I am trying to do, but I cannot get it to work: Angular Stagger Animation for multiple new elements
StackBlitz: https://stackblitz.com/edit/angular-anim-children
My components are:
Parent Component
import { Component } from '@angular/core';
import { trigger, transition, style, animate, query, stagger, group, animateChild } from '@angular/animations';
import { ListService } from '../services/list.service';
@Component({
selector: 'app-list-container',
template: `
<button (click)="add()">Add</button>
<hr>
<app-list-item
*ngFor="let item of list$ | async; let idx = index;"
(delete)="remove(idx)"
[@list]="(list$ | async).length"
[title]="item.title"
[index]="idx"
></app-list-item>
`,
styles: [`
:host {
display: block;
}
`],
animations: [
trigger('list', [
transition('* => *', [
query(':enter', stagger(50, animateChild()), { optional: true })
])
])
]
})
export class ListContainerComponent {
readonly list$ = this.listService.list;
readonly index: number;
constructor(
private readonly listService: ListService,
) { }
add(): void {
const counter = this.listService.counter;
this.listService.add({ title: `Item ${counter + 1}` });
}
remove(index: number): void {
this.listService.remove(index);
}
}
Child Component
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { trigger, transition, style, animate, query, stagger } from '@angular/animations';
@Component({
selector: 'app-list-item',
template: `
<div class="list-item" [@animate]="true">
{{title}}
<button (click)="onDelete(index)">Delete</button>
</div>
`,
styles: [
`
:host {
display: block;
}
button {
margin-left: 20px;
}
`
],
animations: [
trigger('animate', [
transition(':enter', [
style({ opacity: 0, transform: 'translateX(-10px)' }),
animate('250ms', style({ opacity: 1, transform: 'translateX(0px)' }))
]),
transition(':leave', [
style({ opacity: 1 }),
animate('250ms', style({ opacity: 0, transform: 'translateX(10px)' }))
])
])
],
})
export class ListItemComponent {
@Input() title: string;
@Input() index: number;
@Output() delete = new EventEmitter<number>();
onDelete(index: number): void {
this.delete.emit(index);
}
}
Service
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
export interface IItem {
title: string,
}
@Injectable()
export class ListService {
private readonly _list = new BehaviorSubject<IItem[]>([]);
readonly list = this._list.asObservable();
counter = 0;
get length() {
return this._list.value.length;
}
add(item: IItem): void {
this._list.next([item, ...this._list.value]);
this.counter += 1;
}
remove(index: number): void {
this._list.value.splice(index, 1);
}
clear(): void {
this._list.next([]);
}
}
You have a few errors, first, the animate on the parent (list
animation), should look like the following:
trigger("list", [
transition("* => *", [query("@animate", stagger(50, animateChild()))])
])
notice that you need to query the child animation in order to stagger it.
you should add it on a parent of elements with the animate
animation, so something like the following in the template of list-container.component.ts
:
<div @list>
<app-list-item
*ngFor="let item of (list$ | async); let idx = index"
(delete)="remove(idx)"
[title]="item.title"
[index]="idx"
></app-list-item>
</div>
[@animate]="true"
can change into @animate
(this is not error, but redundant)
Here is a working stackblitz