Reputation: 1155
I have a child component with animations
child.component.ts
@Component({
selector: 'child',
animations:[
// animations
]
})
export class ChildComponent { ... }
And a parent component, which has 2 of the child components in the html
parent.component.hmtl.ts
...
<child></child>
<child></child>
...
What I want to achieve is to stagger the animations of the child components in the parent component. Therefore second child component should start animation after X seconds.
animateChild() sounds like it might work but I cant figure out how I can use it. Is that the right solution? If so an example would be really helpful.
Thanks in advance
EDIT: animateChild() does not seem to work for this case. Appearantly it only works on animations that are defined in the parent component.
EDIT2: I think there might be a workaround by adding a delay to the animations inside of the child components.
child.component.ts
@Component({
selector: 'child',
animations:[
animate(x),
// animations
]
})
export class ChildComponent { ... }
The x would be a variable that would increase for every child component. This workaround looks kinda messy to me
EDIT3: the anwers so far are more or less the solution i mentioned in my second edit. While those do work I still consider those as workarounds.
I am looking for a solution that only involves the parent component therefore the child component should stay the way it is like in this non working example
Upvotes: 18
Views: 10111
Reputation: 1820
You can actually use the stagger method, with an animation query, to achieve this:
By adding this:
import { Component } from '@angular/core';
import {
trigger,
style,
animate,
query,
stagger,
transition,
} from '@angular/animations';
const transition1 = trigger('listAnimation', [
transition('* => *', [ // each time the binding value changes
query(':leave', [
stagger(-100, [
animate('0.5s', style({ opacity: 0 }))
])
], { optional: true }),
query(':enter', [
style({ opacity: 0 }),
stagger(500, [
animate('0.5s', style({ opacity: 1 }))
])
], { optional: true })
])
])
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
animations: [
transition1
]
})
export class AppComponent {
items = [];
showItems() {
this.items = [0,1,2,3,4];
}
hideItems() {
this.items = [];
}
toggle() {
this.items.length ? this.hideItems() : this.showItems();
}
}
The only modification you will have to use, is to loop out your child components, which I would have thought, is what you might be planning to do, anyway?
If not just create a dummy array, with the number of items, matching the number of child components, you wish to display.
Notice the negative value of the leave value. This actually reverses the order, in which the items disappear.
The key to this technique, is to control the animation, entirely from the parent component.
Leave the child component to carry out its own animation.
Upvotes: 0
Reputation: 1678
These aren't the only options, but they are ones I know. Angular 7 compatible.
Within your child component, declare your animation state as a host-binding.
@HostBinding('@animationState') animstate : string = 'preanimationstate';
Then use a normal input within your child component to get the delay:
@Input('delay') delay : number;
Pass in the delay like so:
<child-component [delay]="500"></child-component>
So now in the OnInit you can just use a timeout:
let self = this;
window.setTimeout(function () {
self.animstate = 'newstate';
}, self.delay);
Reasoning:
It seems like animate child could also be your saving grace, but this way is pretty straight forward and simple as well. Hope it helps.
You could also move your animation definition (if it is using states and transitions), from child.component.ts to parent.component.ts, then start all of your child components off with an initial state modifier like this:
<child [@animstate]="initial"></child>>
Then get your elements via ViewChildren:
@ViewChildren(ChildComponent) childComponents as QueryList<ChildComponent>;
This is accessible with AfterViewInit. Apply your states within the after view init using a for/each loop and a window.setTimeout
Function.
Upvotes: 4
Reputation: 13801
As per angular documentation in animation function.
The second argument, delay, has the same syntax as duration. For example:
Wait for 100ms and then run for 200ms: '0.2s 100ms'
Full reading here
So, our goal is to pass the second parameter something like this
animate('2000ms {{delay}}ms'
first, lets add input parameter to your child component so we can accept the input values from the parent ones:
export class ChildComponent implements OnInit {
@Input() delay: number = 0;
constructor() { }
}
Now, lets pass down the parameter value form the parent component
<p>child1</p>
<app-child [delay]="0"></app-child>
<p>child2</p>
<app-child [delay]="1000"></app-child>
<p>child2 should have a delay</p>
In your child component we need to pass this parameter to the animation trigger so it would look like this
<p [@childAnimation]="{value:'',params:{delay:delay}}">
IIIIIIIIIIIIIIIIII
</p>
And finally we can change our animation to support this parameter value
animations: [
trigger('childAnimation', [
transition(':enter', [
animate('2000ms {{delay}}ms', style({ transform: 'translateX(80%)' })),
animate('2000ms', style({ transform: 'translateX(0)' })),
], { params: { delay: 0 } })
])
]
all good now, you should working delay to the inputs now.
Upvotes: 13
Reputation: 6001
Just in case anyone stumbles on this issue again: If you want both animations to play at the same time you simply need to use group
and animateChild
. Basically, in the plunker linked in the first post you have to replace outerTransition
with the following:
const outerTransition = transition('void => *', [
style({opacity: 0}),
group([
animate(2000, style({opacity: 1})),
query('@inner', [
animateChild()
]),
]),
]);
Upvotes: 2