Animations are like fluid movements in kata - choreography that brings the application to life.
1// app.config.ts
2import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
3
4export const appConfig: ApplicationConfig = {
5 providers: [
6 provideAnimationsAsync() // Lazy loading of animations
7 ]
8};1import {
2 trigger,
3 transition,
4 style,
5 animate,
6 state,
7 query,
8 stagger,
9 keyframes,
10 group
11} from '@angular/animations';
12
13@Component({
14 selector: 'app-samurai-list',
15 animations: [
16 // Fade in/out
17 trigger('fadeInOut', [
18 transition(':enter', [
19 style({ opacity: 0, transform: 'translateY(-20px)' }),
20 animate('300ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))
21 ]),
22 transition(':leave', [
23 animate('200ms ease-in', style({ opacity: 0, transform: 'translateY(20px)' }))
24 ])
25 ]),
26
27 // Active/inactive state
28 trigger('activeState', [
29 state('inactive', style({
30 transform: 'scale(1)',
31 opacity: 0.8
32 })),
33 state('active', style({
34 transform: 'scale(1.05)',
35 opacity: 1,
36 boxShadow: '0 4px 20px rgba(0,0,0,0.2)'
37 })),
38 transition('inactive <=> active', animate('200ms ease-in-out'))
39 ]),
40
41 // List with stagger effect
42 trigger('listAnimation', [
43 transition('* => *', [
44 query(':enter', [
45 style({ opacity: 0, transform: 'translateX(-50px)' }),
46 stagger('50ms', [
47 animate('300ms ease-out', style({ opacity: 1, transform: 'translateX(0)' }))
48 ])
49 ], { optional: true })
50 ])
51 ])
52 ],
53 template: \`
54 <div [@listAnimation]="samuraiList().length">
55 @for (samurai of samuraiList(); track samurai.id) {
56 <div
57 class="samurai-card"
58 [@fadeInOut]
59 [@activeState]="samurai.id === selectedId() ? 'active' : 'inactive'"
60 (click)="select(samurai.id)"
61 >
62 {{ samurai.name }}
63 </div>
64 }
65 </div>
66 \`
67})
68export class SamuraiListComponent {
69 samuraiList = signal<Samurai[]>([]);
70 selectedId = signal<number | null>(null);
71
72 select(id: number): void {
73 this.selectedId.set(id);
74 }
75}1trigger('attack', [
2 transition('* => attacking', [
3 animate('500ms', keyframes([
4 style({ transform: 'translateX(0)', offset: 0 }),
5 style({ transform: 'translateX(100px) rotate(45deg)', offset: 0.5 }),
6 style({ transform: 'translateX(0)', offset: 1 })
7 ]))
8 ])
9])1trigger('cardFlip', [
2 transition('front => back', [
3 group([
4 animate('300ms', style({ transform: 'rotateY(180deg)' })),
5 query('.front', animate('300ms', style({ opacity: 0 }))),
6 query('.back', animate('300ms', style({ opacity: 1 })))
7 ])
8 ])
9])