23

I have a form component that has fields, some fields I want to animate when they appear in the form, but not every field.

<div *ngFor="let field of form.Fields">
    <div [ngSwitch]="field.Type" [@slideOut]>
        <!-- more field stuff -->
    </div>
</div>

With other attributes I can do something like this [attr.required]="field.Required" But [attr.@slideOut] doesn't seem to work.

Ideally I would like to have an animation property on my field so I could pass in animations like this [@field.Animation] but I can't find any documentation on how I would do anything like this. Any ideas?

jordiburgos
  • 5,964
  • 4
  • 46
  • 80
Devcon
  • 767
  • 2
  • 8
  • 23
  • what are your animation states in your slideOut? You should be using something like [@slideOut]="" or similar, where state_name can be determined dynamically (eg via a method that returns a name) or statically (eg just a hard-coded string) – danimal Apr 14 '17 at 20:27
  • I could try something like that, right now slideOut is a void => * animation. – Devcon Apr 14 '17 at 20:39
  • then to start with set [@slideOut]="'test'" or similar - the * in your animation def should pick it up! – danimal Apr 14 '17 at 20:41

2 Answers2

20

To conditionally enable or disable an animation trigger, use [@.disabled] as documented on https://angular.io/api/animations/trigger#disabling-animations

Taking the example in the OP, if each field has a property 'required' with a boolean value and an animation should only be applied where this is true, the code might be:

<div *ngFor="let field of form.Fields">
    <div [@slideOut] [@.disabled]="!field.required">
        <!-- more field stuff -->
    </div>
</div>

It's worth saying that using [@.disabled] on a component will also disable animations on any child components, e.g. the appearance of a material menu will no longer be animated.

Where necessary, this can be avoided by instead using a conditional to set the duration of the animation on the parent animation to 0, which (visually) removes the animation, without impacting any child components.

Template:

<div *ngFor="let field of form.Fields">
    <div [@slideOut]="field.required ? {value:'', params:{duration : 200}} : {value:'', params:{duration : 0}}">
        <!-- more field stuff -->
    </div>
</div>

Animation:

trigger('slideOut', [
    transition(':enter', [
        style({ opacity: '0' }),
        animate('{{duration}}ms cubic-bezier(0.4, 0.0, 0.2, 1)'),
        style({ opacity: '1' })
    ])
])
Matt Saunders
  • 3,538
  • 2
  • 22
  • 30
19

I've come up against a similar problem and found that you need to take a slightly different approach.

In Angular, animations are linked to state. So instead of defining a different trigger for your animation in your HTML, you want to define multiple states that your trigger can watch for in your component class. And rather than triggering animations directly from a method, you set the state of the object an animation is linked to.

So for instance I might define the following in the component decorator after importing the necessary modules.

@Component({
  selector: 'app-some-animated',
  templateUrl: './some.component.html',
  styleUrls: ['./some-animated.component.scss'],
  animations: [
    trigger('flyInOut', [
      transition("void => fly", [
        animate(300, keyframes([
          style({transform: 'translateX(100%)', opacity: 0}),
          style({transform: 'translateX(0)', opacity: 1})
        ]))
        ]
      )
    ]),
    trigger('flyInOut', [
      transition("void => fade", [
          animate(300, keyframes([
            style({opacity: 0}),
            style({opacity: 1})
          ]))
        ]
      )
    ])
  ]
})

In this instance I'm making the animations happen as soon as the element is instantiated. And in the ngOnInit() method I'm setting this.watchMe to "fly" or "fade".

In the html I'm attaching the flyInOut trigger like so:

<div [@flyInOut]="watchMe">
  Some content...
</div>

In your case you probably want to add the required state to your field objects or as an iterable condition from your *ngFor.

This article from coursetro has a nice explanation and a video too.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
user2528534
  • 203
  • 3
  • 6
  • 3
    I'm accepting this as the answer because its the only way I've found to do this. I don't like having to manage animation state, especially in this case because I will have to do it for each item in an array. I find it weird that angular has these two forms of animations (instance and state). I also don't like how the only attribute I can't dynamically add is made by the angular team :P Ohh well – Devcon Jul 08 '17 at 04:29
  • 4
    Agreed it's a bit of a janky solution. But that's Angular, often feels like you're not really developing just configuring someone else's code. – user2528534 Jul 10 '17 at 12:12
  • could you add an example of the component's function that changes watchme's state, please? – tatsu Feb 12 '18 at 14:39
  • Thanks for the answer. just a note : we need use variables (for ex if we use [@flyInOut]="'fly'" it will not work as we expect) – Smaillns Aug 03 '21 at 08:24