12

I have a list, where the items have an animation like this:

<li @animation>

And this is my animation trigger:

trigger('animation', [
  transition(':enter', [
    style({ height: '0px', 'padding-top': '0', 'padding-bottom': '0'}),  // initial
    animate('0.5s',
      style({ height: '*', 'padding-top': '*', 'padding-bottom': '*'}))  // final
  ]),
  transition(':leave', [
    style({ height: '*', 'padding-top': '*', 'padding-bottom': '*', opacity: 1}),  // initial
    animate('0.5s',
      style({ height: '0px', 'padding-top': '0', 'padding-bottom': '0', opacity: 0}))  // final
  ])
])

How can i conditionally turn on/off this animation for a specific item? Actually i look for sth. like this:

<li [@animation]=item.isAnimated>

which does not work at all.

And unfortunately Angular documentation has just a sentence about this:

For elements entering or leaving a page (inserted or removed from the DOM), you can make the animations conditional. For example, use *ngIf with the animation trigger in the HTML template.

But when i combine the animation annotation with a *ngIf, the not-animated items will obviously not be shown at all:

<li *ngIf="item.isAnimated" @animation>

I want to show all the items further on regardless of isAnimated flag. I just want to turn on/off the animation for a specific item.

akcasoy
  • 6,497
  • 13
  • 56
  • 100

3 Answers3

15

According to Angular IO:

When true, the special animation control binding @.disabled binding prevents all animations from rendering. Place the @.disabled binding on an element to disable animations on the element itself, as well as any inner animation triggers within the element.

The following example shows how to use this feature:

@Component({
    selector: 'my-component',
    template: `
    <div [@.disabled]="isDisabled">
        <div [@childAnimation]="exp"></div>
    </div>
    `,
    animations: [
        trigger("childAnimation", [
            // ...
        ])
    ]
})
class MyComponent {
      isDisabled = true;
  exp = '...';
}

When @.disabled is true, it prevents the @childAnimation trigger from animating, along with any inner animations.

j4rey
  • 2,582
  • 20
  • 34
5

Create a no-op animation trigger and insert it before :enter

I think the [@.disabled] approach is fine, but it feels odd to create something and disable it via a different mechanism. Fortunately there's an even simpler way.


First, it's important to remember that :enter is exactly equivalent to void => *. In fact here it is in the Angular source code.

function parseAnimationAlias(alias: string, errors: string[]): string|TransitionMatcherFn {
  switch (alias) {
    case ':enter':
      return 'void => *';
    case ':leave':
      return '* => void';
    case ':increment':
      return (fromState: any, toState: any): boolean => parseFloat(toState) > parseFloat(fromState);
    case ':decrement':
      return (fromState: any, toState: any): boolean => parseFloat(toState) < parseFloat(fromState);
    default:
      errors.push(`The transition alias value "${alias}" is not supported`);
      return '* => *';
  }
}

Simplest solution

It's also important to know that Angular's animation engine only matches the first trigger and runs it.

Therefore all you need to do is create a new trigger and insert it before :enter. This animation is a no-op.

transition('void => false', []),
transition(':enter', [...])

And for :leave

transition('false => void', []),
transition(':leave', [...])

So your animation in the template becomes:

[@animation]="enableAnimations"

And when it's false it'll match and do nothing. ANY other value would match :enter which is of course really void => *.

The beauty of this is it won't break anything that already uses [@animation] because that still matches void => * as before.

Note: Any (done) handlers will still get executed even after the no-op animation - so if you're using one make sure it does the right thing when there was no animation.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
2

To clarify: Angulars :enter and :leave keywords are to animate components if the are entering or leaving the dom. Sounds simple but that's exactly the issue with your approach and the aim you try to achieve. Instead of just animating, if there is a new element in the dom you want it more customized, so therefore you need own states, wich can be controlled by yourself in the ngOnInit and ngOnDestroy of a list-entry.

A approach could be the following:

@Component({
  animations: [
    trigger('animation', [
      state('invisible', style({ height: '0px', 'padding-top': '0', 'padding-bottom': '0'}),
      state('visible', style({ height: '*', 'padding-top': '*', 'padding-bottom': '*'})
      transition('invisible => visible', animate('0.5s'))
      transition('visible => invisible', animate('0.5s'))
    ])
  ],
})

private readonly isAnimated: boolean = false/true //Where ever you get this value.
public animationState: string //Or Enum with visible/invisible.

public ngOnInit(): void {
  if (this.isAnimated) {
    animationState = 'visible'
  }
}

public ngOnDestroy(): void {
  if (this.isAnimated && this.animationState === 'visible') {
    animationState = 'invisible'
  }
}
<li [@animation]="animationState"/>

If there are any more questions or issues with this approach - let me know and we can adjust and discuss.

Jonathan Stellwag
  • 3,843
  • 4
  • 25
  • 50
  • the items do not have their own components.. that is why i have to manually build the logic you suggest with ngOnInit and ngOnDestroy.. i would like to wait a bit more if someone has any other idea which works with my current code. Thank you! ;) – akcasoy Feb 25 '19 at 09:36
  • No problem - But if you need individual control's for component it might makes sence instead of using a
  • to make your own small component for this usecase. Nevertheless I am also interested if there is a angular out-of-the-box solution. :)
  • – Jonathan Stellwag Feb 25 '19 at 10:11