Angular Animations - aakash14goplani/FullStack GitHub Wiki

Topics Covered


Introduction

  • If we have a scenario to attach or detach DOM elements, apply custom transitions and effects, all these things are bit hard to achieve via CSS because as your DOM is dynamically generated by Angular, they may not be available when your CSS is triggered. To achieve animations in such cases Angular ships with default package - Animation

Setting Triggers and States

  • Initial Configurations:

    • Install animations package npm install --save @angular/animations
    • Add BrowserAnimationsModule imported from @angular/platform-browser/animations to imports[] in app.module.ts file
  • Use Case: We need to achieve some animation on certain div on button click

  • Remember Animations are just transition of effects/commands from State A to State B, where State A is your initial state and State B is the final state.

  • Animations are basically defined by animations array within meta-data in Component file

    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
      animations: [...]
    })
  • Each animation has trigger which accepts an argument that is basically a reference to DOM element. So in DOM we have certain element with a particular reference which in turn triggers this Animation

    <div style="width: 100px; height: 100px;" [@divState]="state"></div>
    trigger('divState', [...])
    ...
    export class AppComponent {
      state = "normal";
    }
    • here we have [@divState]="state" as selector in DOM which triggers this animation. This selector is set to a property state
  • Within triggers we define state which is an JavaScript object where we can define initial state and final state and CSS effects that need to performed on those states

    animations: [
     trigger('divState', [
       state('normal', style({
         'background-color': 'black',
         'transform': 'translateX(0)'
       })),
       state('highlighted', style({
         'background-color': 'red',
         'transform': 'translateX(100)'
       }))
     ])
    ]
    • the name of initial state is set to normal which is exactly same as the initial property that was set to our selector i.e. [@divState]="state" where state="normal"
    • the name of final state is set to highlighted which is the goal that we want to achieve - highlight certain div based on particular actions
    • All of these methods trigger(), state() and style() are imported from @angular/animations

Switching between States

We can switch between states by changing / toggling the states name from normal to highlighted

<button class="btn btn-primary" (click)="onAnimate()">Animate!</button>
state = 'normal';
onAnimate() {
   this.state === 'normal' ? this.state = 'highlighted' : this.state = 'normal';
}

Transitions

We can add simple transitions to or animations for that we need transitions() method that takes two arguments. First is the expression from t state to final state and second argument is the time amount to perform this animation which is achieved by animate() method

animations: [
   trigger('divState', [
      state('normal', style({
        'background-color': 'black',
        'transform': 'translateX(0)'
      })),
      state('highlighted', style({
        'background-color': 'red',
        'transform': 'translateX(100px)'
      })),
      transition('normal => highlighted', animate(300)),
      transition('highlighted => normal', animate(600))
   ])
]

Complex Transitions

If we have more complex use case in which we have to achieve all of the things mentioned above plus some in-between animations like when we move from state A to State B, we need some extra transitions like DOM element getting scaled and de-scaled.

We set new DOM element to show this 3rd transition:

<button class="btn btn-primary" (click)="onAnimate()">Animate!</button>
<button class="btn btn-primary" (click)="onShrink()">Shrink!</button>
...
<div style="width: 100px; height: 100px;" [@divState]="state"></div>
<div style="width: 100px; height: 100px;" [@wildState]="wildState"></div>

We need to add new trigger to accommodate this

trigger('wildState', [
   state('normal', style({
       'background-color': 'black',
       'transform': 'translateX(0) scale(1)'
   })),
   state('highlighted', style({
       'background-color': 'red',
       'transform': 'translateX(100px) scale(1)'
   })),
   state('shrunken', style({
       'background-color': 'green',
       'transform': 'translateX(0) scale(0.5)'
   }))
])

We also add corresponding transitions

transition('normal => highlighted', animate(300)),
transition('highlighted => normal', animate(600)),
transition('shrunken <=> *', animate(900))

Here the new transition expression states that - if me move from shrunken state to any (*) or from any (*) state to shrunken state, perform this animation

state = 'normal';
wildState = 'normal';
...
onAnimate() {
   this.state === 'normal' ? this.state = 'highlighted' : this.state = 'normal';
   this.wildState === 'normal' ? this.wildState = 'highlighted' : this.wildState = 'normal';
}
onShrink() {
   this.wildState = 'shrunken';
}

Transition Phases

If we plan to carry out multiple transitions while the animations is still on...

...
transition('shrunken <=> *', [
   style({
      'background-color': 'orange'
   }),
   animate(400, style({
       'border-radius': '50px'
   })),
   animate(400)
])

Above code means:

  • instantly change color of element to orange
  • wait for 400 ms and change border radius to 50px
  • move to next state after waiting for other 400 ms

Void State

  • Use Case: Add - Delete items to and from list with animation

Here we make use of void state which is the special keyword reserved by Angular which is very useful when you want to perform operations on a non-exiting state to any state. Or to perform an operation of an element which wasn't present at the beginning.

We bind the HTML list with the new trigger

<ul class="list-group">
   <li class="list-group-item" [@list1] (click)="onDelete(item)" *ngFor="let item of list">
       {{ item }}
   </li>
</ul>

Create corresponding trigger

trigger('list1', [
   state('in', style({
      'opacity': 1,
      'transform': 'translateX(0)'
   })),
   transition('void => *', [
      style({
         'opacity': 0,
         'transform': 'translateX(-100px)'
      }),
      animate(300)
   ]),
   transition('* => void', [
      animate(300, style({
         'transform': 'translateX(100px)',
         'opacity': 0
      }))
   ])
])
  • When we add element to list it will get instant style and then transitions void => * get applied with some animation wait of 300 ms
  • When we delete element from list, we already have initial state, so we transit from * => void and apply some styles but this time within animation

Keyframes

  • We want to have fine grain control over animation / transition i.e. Animation A should run for x ms and Animation B should run for y ms - in such use cases we can add keyframes to our animation
transition('void => *', [
   animate(1000, keyframes([
      style({
         transform: 'translateX(-100px)',
         opacity: 0,
         offset: 0
      }),
      style({
         transform: 'translateX(-50px)',
         opacity: 0.5,
         offset: 0.3
      }),
      style({
         transform: 'translateX(-20px)',
         opacity: 1,
         offset: 0.8
      }),
      style({
          transform: 'translateX(0px)',
          opacity: 1,
          offset: 1
      })
   ]))
])

Grouping Transitions

  • If you want to group multiple animations with possibly same or different timing you can use group() method
    transition('* => void', [
       group([
          animate(300, style({
             color: 'red'
          })),
          animate(800, style({
             transform: 'translateX(100px)',
             opacity: 0
          }))
       ])
    ])

Animation Callbacks

  • If an animation finish and you want to execute some other code - you could do this with help of callbacks

  • We can register callbacks in our DOM

    <div style="width: 100px; height: 100px;" [@divState]="state" (@divState.start)="animationStarted($event)" (@divState.done)="animationEnded($event)"></div>
  • Syntax is like event-binding and starting with @ to indicate that it is related to angular animations and bind it to particular method. start will fire when Animation triggers on divState and done will fire whrn animtion finally ends

    animationStarted(event) {
     console.log(event);
    }
    
    animationEnded(event) {
     console.log(event);
    }
⚠️ **GitHub.com Fallback** ⚠️