Angular Animations

Question 1: What are Angular animations and how do you implement them?

Answer: Angular animations provide a powerful system for implementing animations declaratively. Key concepts include:

  1. State and transition animations
  2. Keyframe animations
  3. Group animations
  4. Reusable animations
  5. Route animations

Question 2: How do you implement basic animations in Angular?

Answer: Here are examples of different animation types:

// 1. Basic State Animation
import { 
  trigger, 
  state, 
  style, 
  animate, 
  transition 
} from '@angular/animations';

@Component({
  selector: 'app-animated-card',
  template: `
    <div [@cardState]="state" 
         (click)="toggleState()">
      <ng-content></ng-content>
    </div>
  `,
  animations: [
    trigger('cardState', [
      state('normal', style({
        transform: 'scale(1)',
        opacity: 1
      })),
      state('expanded', style({
        transform: 'scale(1.1)',
        opacity: 0.9
      })),
      transition('normal <=> expanded', [
        animate('200ms ease-in-out')
      ])
    ])
  ]
})
export class AnimatedCardComponent {
  state = signal<'normal' | 'expanded'>('normal');
  
  toggleState() {
    this.state.update(current => 
      current === 'normal' ? 'expanded' : 'normal'
    );
  }
}

// 2. Keyframe Animation
@Component({
  selector: 'app-shake-button',
  template: `
    <button [@shakeAnimation]="shake()"
            (click)="triggerShake()">
      Click Me!
    </button>
  `,
  animations: [
    trigger('shakeAnimation', [
      transition('false => true', [
        animate('500ms', keyframes([
          style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.1 }),
          style({ transform: 'translate3d(10px, 0, 0)', offset: 0.3 }),
          style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.5 }),
          style({ transform: 'translate3d(10px, 0, 0)', offset: 0.7 }),
          style({ transform: 'translate3d(0, 0, 0)', offset: 1 })
        ]))
      ])
    ])
  ]
})
export class ShakeButtonComponent {
  shake = signal(false);
  
  triggerShake() {
    this.shake.set(true);
    setTimeout(() => this.shake.set(false), 500);
  }
}

Question 3: How do you implement complex animations?

Answer: Here are examples of more complex animations:

// 1. Group Animations
@Component({
  selector: 'app-complex-animation',
  template: `
    <div [@complexAnimation]="state()">
      <div class="card">
        <h2>{{ title }}</h2>
        <p>{{ content }}</p>
      </div>
    </div>
  `,
  animations: [
    trigger('complexAnimation', [
      transition('void => *', [
        group([
          query('.card', [
            style({ transform: 'translateY(-100%)' }),
            animate('300ms ease-out', 
              style({ transform: 'translateY(0)' }))
          ]),
          query('h2', [
            style({ opacity: 0, transform: 'translateX(-100%)' }),
            animate('300ms 100ms ease-out', 
              style({ opacity: 1, transform: 'translateX(0)' }))
          ]),
          query('p', [
            style({ opacity: 0, transform: 'translateX(100%)' }),
            animate('300ms 200ms ease-out', 
              style({ opacity: 1, transform: 'translateX(0)' }))
          ])
        ])
      ])
    ])
  ]
})
export class ComplexAnimationComponent {
  state = signal<string>('active');
}

// 2. Reusable Animations
// animations.ts
export const fadeAnimation = trigger('fade', [
  transition(':enter', [
    style({ opacity: 0 }),
    animate('300ms', style({ opacity: 1 }))
  ]),
  transition(':leave', [
    animate('300ms', style({ opacity: 0 }))
  ])
]);

export const slideAnimation = trigger('slide', [
  transition(':enter', [
    style({ transform: 'translateX(-100%)' }),
    animate('300ms ease-out', 
      style({ transform: 'translateX(0)' }))
  ]),
  transition(':leave', [
    animate('300ms ease-in', 
      style({ transform: 'translateX(100%)' }))
  ])
]);

// Usage in component
@Component({
  selector: 'app-animated',
  template: `
    @if (show()) {
      <div @fade>
        Faded content
      </div>
    }
    
    @if (items(); as list) {
      @for (item of list; track item.id) {
        <div @slide>
          {{ item.name }}
        </div>
      }
    }
  `,
  animations: [fadeAnimation, slideAnimation]
})
export class AnimatedComponent {
  show = signal(true);
  items = signal<Item[]>([]);
}

// 3. Route Animations
@Component({
  selector: 'app-root',
  template: `
    <div [@routeAnimations]="
      getRouteState(outlet)
    ">
      <router-outlet #outlet="outlet" />
    </div>
  `,
  animations: [
    trigger('routeAnimations', [
      transition('* <=> *', [
        style({ position: 'relative' }),
        query(':enter, :leave', [
          style({
            position: 'absolute',
            top: 0,
            left: 0,
            width: '100%'
          })
        ], { optional: true }),
        query(':enter', [
          style({ left: '-100%' })
        ], { optional: true }),
        query(':leave', animateChild(), { optional: true }),
        group([
          query(':leave', [
            animate('300ms ease-out', 
              style({ left: '100%' }))
          ], { optional: true }),
          query(':enter', [
            animate('300ms ease-out', 
              style({ left: '0%' }))
          ], { optional: true })
        ])
      ])
    ])
  ]
})
export class AppComponent {
  getRouteState(outlet: RouterOutlet) {
    return outlet?.activatedRouteData?.['animation'];
  }
}

Question 4: How do you optimize animations for performance?

Answer: Here are performance optimization techniques:

// 1. Web Animations API
@Component({
  animations: [
    trigger('performantAnimation', [
      transition('* => *', [
        animate('{{ duration }}ms {{ timing }}', 
          style({ transform: '{{ transform }}' }))
      ], {
        params: {
          duration: 300,
          timing: 'ease-out',
          transform: 'translateX(0)'
        }
      })
    ])
  ]
})
export class PerformantComponent {
  // Use transform instead of left/top
  animationState = signal({
    duration: 300,
    timing: 'ease-out',
    transform: 'translateX(100%)'
  });
}

// 2. Optimized List Animations
@Component({
  template: `
    @for (item of items(); track item.id) {
      <div @listAnimation>
        {{ item.name }}
      </div>
    }
  `,
  animations: [
    trigger('listAnimation', [
      transition(':enter', [
        style({ 
          opacity: 0, 
          transform: 'translateY(-20px)' 
        }),
        animate('200ms ease-out', style({ 
          opacity: 1, 
          transform: 'translateY(0)' 
        }))
      ])
    ])
  ]
})
export class ListComponent {
  items = signal<Item[]>([]);
}

Interview Tips 💡

  1. Animation States

    // Define clear states
    trigger('elementState', [
      state('inactive', style({
        backgroundColor: '#eee',
        transform: 'scale(1)'
      })),
      state('active', style({
        backgroundColor: '#cfd8dc',
        transform: 'scale(1.1)'
      })),
      transition('inactive => active', animate('100ms ease-in')),
      transition('active => inactive', animate('100ms ease-out'))
    ])
  2. Reusable Animations

    // Create animation factory
    export const slideInAnimation = (
      duration: number = 300
    ) => 
      trigger('slideIn', [
        transition(':enter', [
          style({ transform: 'translateX(-100%)' }),
          animate(`${duration}ms ease-out`, 
            style({ transform: 'translateX(0)' }))
        ])
      ]);
  3. Performance Tips

    // Use transform and opacity
    trigger('performant', [
      state('visible', style({
        opacity: 1,
        transform: 'translateX(0)'
      })),
      state('hidden', style({
        opacity: 0,
        transform: 'translateX(-100%)'
      }))
    ])
  4. Testing

    describe('AnimatedComponent', () => {
      it('should trigger animation', fakeAsync(() => {
        const fixture = TestBed.createComponent(
          AnimatedComponent
        );
        const component = fixture.componentInstance;
        
        component.state.set('active');
        fixture.detectChanges();
        
        tick(300); // Animation duration
        expect(component.state()).toBe('active');
      }));
    });
  5. Common Patterns

    // Stagger animations
    query(':enter', stagger('50ms', [
      style({ opacity: 0, transform: 'translateY(-20px)' }),
      animate('200ms ease-out', 
        style({ opacity: 1, transform: 'translateY(0)' }))
    ]))
  6. Best Practices

    // Use params for flexibility
    trigger('parameterized', [
      transition('* => *', [
        animate('{{ duration }}ms {{ timing }}')
      ], {
        params: {
          duration: 300,
          timing: 'ease-out'
        }
      })
    ])

Remember: In interviews, focus on:

  • Animation concepts
  • Implementation patterns
  • Performance optimization
  • Testing strategies
  • Best practices
  • Common use cases
  • Browser support

Key points to emphasize:

  1. State-based animations
  2. Keyframe animations
  3. Route animations
  4. Performance optimization
  5. Testing approaches
  6. Reusability
  7. Best practices

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your Angular Knowledge

Ready to put your skills to the test? Take our interactive Angular quiz and get instant feedback on your answers.