Angular Animation Basics
Triggers
Animation triggers are the core of Angular animations, defining when and how animations occur. Triggers are bound to DOM elements and can include multiple states and transitions.
// app.component.ts
import { Component } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-root',
template: `
<div [@flyInOut]="isShown ? 'in' : 'out'" (click)="toggle()">
Click me!
</div>
`,
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })),
state('out', style({ transform: 'translateX(-100%)' })),
transition('in => out', animate('0.5s ease-out')),
transition('out => in', animate('0.5s ease-in'))
])
]
})
export class AppComponent {
isShown = true;
toggle() {
this.isShown = !this.isShown;
}
}States
States represent specific phases of an animation. In the example above, in and out are two states, each defining the element’s styles.
Transitions
Transitions define the animation effect between states. For instance, transition('in => out', animate('0.5s ease-out')) specifies a 0.5-second animation with an ease-out easing function when moving from the in state to the out state.
Animation Queries and Groups
Animation queries allow referencing other animations, while animation groups enable running multiple animations simultaneously.
// app.component.ts
import { trigger, state, style, transition, animate, group, query, animateChild } from '@angular/animations';
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })),
state('out', style({ transform: 'translateX(-100%)' })),
transition('in => out', animate('0.5s ease-out')),
transition('out => in', animate('0.5s ease-in'))
]),
trigger('fade', [
state('in', style({ opacity: 1 })),
state('out', style({ opacity: 0 })),
transition('in => out', animate('0.5s ease-out')),
transition('out => in', animate('0.5s ease-in'))
]),
group([
query('@flyInOut', animateChild()),
query('@fade', animateChild())
])
]Advanced Animation Usage
Animation Parameters
Animation parameters enable dynamic adjustments to aspects like duration or easing functions.
// app.component.ts
import { Component, Input } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-root',
template: `...`,
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })),
state('out', style({ transform: 'translateX(-100%)' })),
transition('in => out', animate('{{duration}}ms ease-out', { params: { duration: 1000 } })),
transition('out => in', animate('{{duration}}ms ease-in', { params: { duration: 1000 } }))
])
]
})
export class AppComponent {
@Input() duration: number = 1000; // Animation duration in milliseconds
}Animation Events
Animation events allow executing code at different animation stages, such as start or end.
// app.component.ts
import { Component, HostListener } from '@angular/core';
@Component({
selector: 'app-root',
template: `...`
})
export class AppComponent {
@HostListener('animationstart', ['$event'])
onAnimationStart(event: AnimationEvent) {
console.log('Animation started:', event);
}
@HostListener('animationend', ['$event'])
onAnimationEnd(event: AnimationEvent) {
console.log('Animation ended:', event);
}
}Animations in Real-World Projects
In real projects, you might animate list items, apply fade-in/fade-out effects to modals, or add sliding effects to navigation menus. Below is an example of animating list items:
// app.component.ts
import { Component } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-root',
template: `
<ul>
<li *ngFor="let item of items" [@listItem]="item.status">
{{ item.text }}
</li>
</ul>
`,
animations: [
trigger('listItem', [
state('void', style({ opacity: 0, transform: 'scale(0.5)' })),
state('visible', style({ opacity: 1, transform: 'scale(1)' })),
transition(':enter', animate('0.5s ease-out')),
transition(':leave', animate('0.5s ease-in'))
])
]
})
export class AppComponent {
items = [
{ text: 'Item 1', status: 'visible' },
{ text: 'Item 2', status: 'visible' },
{ text: 'Item 3', status: 'visible' }
];
addItem() {
this.items.push({ text: `New Item ${this.items.length + 1}`, status: 'void' });
setTimeout(() => {
this.items[this.items.length - 1].status = 'visible';
}, 0);
}
}Animations with Routing
Angular’s animation system integrates seamlessly with the routing system, enabling smooth transitions between pages.
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
const routes: Routes = [
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'about', component: AboutComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }// app.component.ts
import { Component } from '@angular/core';
import { routeAnimation } from './route-animation';
@Component({
selector: 'app-root',
template: `
<router-outlet></router-outlet>
`,
animations: [routeAnimation]
})
export class AppComponent { }// route-animation.ts
import { trigger, state, style, transition, animate } from '@angular/animations';
export const routeAnimation = trigger('routeAnimation', [
state('*', style({ position: 'relative' })),
transition('* => isLeft', slideTo('left')),
transition('* => isRight', slideTo('right')),
transition('isLeft => *', slideFrom('left')),
transition('isRight => *', slideFrom('right')),
]);
function slideTo(direction: string) {
return [
style({ position: 'relative' }),
animate('0.5s ease-out', style({ transform: `translateX(${direction === 'left' ? '-100%' : '100%'})` }))
];
}
function slideFrom(direction: string) {
return [
style({ position: 'relative', transform: `translateX(${direction === 'left' ? '-100%' : '100%'})` }),
animate('0.5s ease-in', style({ transform: `translateX(0%)` }))
];
}Animation Testing
Testing animations ensures consistent behavior across scenarios. Use Angular’s testing tools like Karma and Jasmine for unit and end-to-end animation tests.
// animation.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { AnimationComponent } from './animation.component';
describe('AnimationComponent', () => {
let component: AnimationComponent;
let fixture: ComponentFixture<AnimationComponent>;
let de: DebugElement;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AnimationComponent]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AnimationComponent);
component = fixture.componentInstance;
de = fixture.debugElement.query(By.css('.animated-element'));
fixture.detectChanges();
});
it('should animate on click', () => {
de.triggerEventHandler('click', null);
fixture.whenStable().then(() => {
expect(de.nativeElement.style.transform).toBe('translateX(100%)');
});
});
});Animation Collaboration and State Machines
In complex UI scenarios, animation states may depend on multiple conditions. State machines, supported by @angular/animations, enable complex animation logic.
// app.component.ts
import { Component } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-root',
template: `
<button (click)="toggle()">Toggle</button>
<div [@stateMachine]="state"></div>
`,
animations: [
trigger('stateMachine', [
state('state1', style({ backgroundColor: 'red' })),
state('state2', style({ backgroundColor: 'blue' })),
transition('state1 => state2', animate('0.5s ease-in')),
transition('state2 => state1', animate('0.5s ease-out'))
])
]
})
export class AppComponent {
state = 'state1';
toggle() {
this.state = this.state === 'state1' ? 'state2' : 'state1';
}
}Responsive Animations
In responsive design, animations may need to adapt based on screen size or device type. Use media queries or Angular’s ViewportRuler for responsive animations.
// responsive.component.ts
import { Component, OnInit } from '@angular/core';
import { ViewportRuler } from '@angular/cdk/overlay';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-responsive',
template: `
<div [@responsiveAnimation]="animationState"></div>
`,
animations: [
trigger('responsiveAnimation', [
state('small', style({ width: '50px' })),
state('large', style({ width: '200px' })),
transition('small => large', animate('0.5s')),
transition('large => small', animate('0.5s'))
])
]
})
export class ResponsiveComponent implements OnInit {
animationState = 'small';
constructor(private viewportRuler: ViewportRuler) {}
ngOnInit() {
this.viewportRuler.getViewportSize().subscribe(size => {
this.animationState = size.width < 600 ? 'small' : 'large';
});
}
}Animations and Performance
While animations enhance user experience, excessive animations in large applications can negatively impact performance. Here are strategies to optimize animation performance:
- Use requestAnimationFrame: Ensure animations update on each frame, not on every render.
- Avoid Forced Synchronous Layouts: Refrain from modifying DOM styles directly in animations, as this triggers layout recalculations.
- Use CSS Animations Instead: For simple effects, CSS animations may be more efficient, leveraging hardware acceleration.
- Minimize Repaints and Reflows: Avoid animating layout-related properties (e.g.,
width,height,top,left), which cause reflows and repaints. - Use Compositing Layers: Apply animations to
transformoropacityproperties to leverage GPU acceleration. - Lazy Load Animations: For non-visible animations, use lazy loading to load resources only when needed.
Case Study: Dynamic Loading Animations
In some cases, you may want to load animations dynamically based on runtime conditions. This can be achieved using AnimationBuilder and Renderer2.
// dynamic.component.ts
import { Component, Renderer2, ElementRef, ViewChild } from '@angular/core';
import { AnimationBuilder, AnimationPlayer, style, animate, keyframes } from '@angular/animations';
@Component({
selector: 'app-dynamic',
template: `
<div #dynamicElement></div>
`
})
export class DynamicComponent {
@ViewChild('dynamicElement') dynamicElement: ElementRef;
constructor(
private renderer: Renderer2,
private animationBuilder: AnimationBuilder
) {}
loadAnimation() {
const factory = this.animationBuilder.build([
animate('1s', keyframes([
style({ opacity: 0, offset: 0 }),
style({ opacity: 1, offset: 1 })
]))
]);
const player: AnimationPlayer = factory.create(this.dynamicElement.nativeElement);
player.play();
}
}



