#angular #animations #transitions #ui

Day 21 — Angular Animations & Transitions

📘 Day 21 — Angular Animations: Complex Transitions & Staggering

Zero to Hero — Hands-on Angular Tutorial

Today you will learn:

  • ✔️ Setup: Enabling the Animation Engine
  • ✔️ Triggers: :enter and :leave (Fade In/Out)
  • ✔️ States: Animating between styles (open vs. closed)
  • ✔️ Query & Stagger: Animating lists items one-by-one
  • ✔️ Keyframes: Multi-step complex movements

Angular’s animation system is built on top of the Web Animations API. It allows you to coordinate complex sequences that are impossible with simple CSS classes. 🎬


🟦 1. Enable Animations

In modern Angular (Standalone), add provideAnimations() to your config.

app.config.ts:

import { provideAnimations } from '@angular/platform-browser/animations';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideAnimations() // 👈 Turn on the engine
  ]
};

🟩 2. Fade In / Fade Out (:enter & :leave)

The most common use case: Animate an element when *ngIf shows or hides it.

app.component.ts:

import { Component } from '@angular/core';
import { trigger, style, animate, transition } from '@angular/animations';
import { NgIf } from '@angular/common';

@Component({
  standalone: true,
  imports: [NgIf],
  template: `
    <button (click)="show = !show">Toggle</button>
    
    <!-- Apply the trigger '@fadeInOut' -->
    <div *ngIf="show" @fadeInOut class="box">
      I Am Visible!
    </div>
  `,
  animations: [
    trigger('fadeInOut', [
      // :enter is alias for 'void => *'
      transition(':enter', [
        style({ opacity: 0, transform: 'scale(0.8)' }), // Start style
        animate('300ms ease-out', style({ opacity: 1, transform: 'scale(1)' })) // End style
      ]),
      // :leave is alias for '* => void'
      transition(':leave', [
        animate('200ms ease-in', style({ opacity: 0, transform: 'scale(0.8)' }))
      ])
    ])
  ]
})
export class AppComponent {
  show = false;
}
  • ✔️ @fadeInOut: Binds the trigger to the HTML element.
  • ✔️ :enter: Runs when element is added to DOM.
  • ✔️ :leave: Runs when element is removed from DOM.

🟧 3. State-Based Animation (Open/Closed)

Use this for elements that stay in the DOM but change appearance (like an Accordion or Sidebar).

import { state, ... } from '@angular/animations';

animations: [
  trigger('openClose', [
    state('open', style({
      height: '200px',
      opacity: 1,
      backgroundColor: 'yellow'
    })),
    state('closed', style({
      height: '0px',
      opacity: 0,
      backgroundColor: 'grey'
    })),
    transition('open <=> closed', [
      animate('0.5s cubic-bezier(0.4, 0.0, 0.2, 1)')
    ])
  ])
]

HTML:

<div [@openClose]="isOpen ? 'open' : 'closed'">
  I am an accordion!
</div>

🟥 4. List Staggering (The “Wow” Factor)

This is the coolest feature. When you look at a list of items, you want them to fly in one by one, not all at once.

Requires query() and stagger():

import { trigger, transition, style, animate, query, stagger } from '@angular/animations';

animations: [
  trigger('listAnimation', [
    transition('* => *', [ // Run whenever the list data changes
      // 1. Find all elements entering the DOM (:enter)
      query(':enter', [
        style({ opacity: 0, transform: 'translateY(-20px)' }),
        // 2. Stagger them by 100ms
        stagger('100ms', [
          animate('300ms ease-out', style({ opacity: 1, transform: 'none' }))
        ])
      ], { optional: true }) // Optional incase list is empty at start
    ])
  ])
]

HTML:

<div [@listAnimation]="items.length">
  <div *ngFor="let item of items" class="list-item">
    {{ item }}
  </div>
</div>

🟫 5. Keyframes (Complex Movement)

Standard animate() goes from Point A to Point B. keyframes allows Point A -> B -> C -> D.

Example: Shake Effect (Error)

import { keyframes } from '@angular/animations';

transition('* => error', [
  animate('500ms', keyframes([
    style({ transform: 'translateX(0)', offset: 0 }),
    style({ transform: 'translateX(-10px)', offset: 0.2 }), // Shake left
    style({ transform: 'translateX(10px)', offset: 0.4 }),  // Shake right
    style({ transform: 'translateX(-10px)', offset: 0.6 }), // Shake left
    style({ transform: 'translateX(0)', offset: 1 })
  ]))
])

🎉 End of Day 21 — What You Learned

Today you made your app feel “Premium”:

  • ✔️ :enter / :leave: Smoothly adding/removing elements.
  • ✔️ stagger: Professional list loading effects.
  • ✔️ states: Animating component properties (Expand/Collapse).
  • ✔️ keyframes: Custom multi-stage effects.

🧪 Day 21 Challenge

Build a “Toast Notification” System.

Requirements:

  1. Create a ToastComponent that accepts a message.
  2. Use a Trigger called flyInOut.
  3. Enter: Slice in from the right (translateX(100%) -> translateX(0)).
  4. Leave: Fade out opacity (1 -> 0).
  5. Position it fixed top-right.
  6. Click a button to add random toasts to an array and watch them animate in.