Flyii
Flyii

Reputation: 1155

Angular how to delay/stagger the animation inside of child components

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>
...

Stackblitz Example

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

Answers (4)

Charles Robertson
Charles Robertson

Reputation: 1820

You can actually use the stagger method, with an animation query, to achieve this:

demo

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

Terrance00
Terrance00

Reputation: 1678

These aren't the only options, but they are ones I know. Angular 7 compatible.

Working Stackblitz Demo

Option 1: Host Binding & States

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.

Option 2: Move animation to HostComponent and manually apply state

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

Just code
Just code

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.

Check out demo here

Upvotes: 13

Ismoil  Shifoev
Ismoil Shifoev

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

Related Questions