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:
- State and transition animations
- Keyframe animations
- Group animations
- Reusable animations
- 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 💡
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')) ])
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)' })) ]) ]);
Performance Tips
// Use transform and opacity trigger('performant', [ state('visible', style({ opacity: 1, transform: 'translateX(0)' })), state('hidden', style({ opacity: 0, transform: 'translateX(-100%)' })) ])
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'); })); });
Common Patterns
// Stagger animations query(':enter', stagger('50ms', [ style({ opacity: 0, transform: 'translateY(-20px)' }), animate('200ms ease-out', style({ opacity: 1, transform: 'translateY(0)' })) ]))
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:
- State-based animations
- Keyframe animations
- Route animations
- Performance optimization
- Testing approaches
- Reusability
- Best practices