CSS animations trigger events that we can intercept with JavaScript. The way we wire up these event listeners varies between a plain JavaScript setup and an Angular project. Let's first look at the events that are available.
animationstart— Fires when the animation begins.animationend— Fires when the animation finishes.animationiteration— Fires each time an iteration of the animation finishes and a new one starts. This event does not fire for the final iteration — instead, theanimationendevent is triggered.
Note that the event names are all lowercase, not camel-case.
General Usage
To capture these events, we attach an event listener to the animating element using addEventListener. This method is not animation-specific; it works for any DOM event. We can pass any event from the full list of supported events.
Approach 1: Using addEventListener
This standard JavaScript technique works in Angular without changes. The only distinction is how Angular references the target element. Instead of getViewById, Angular uses the ViewChild decorator.
<!-- src/app/app.component.html -->
<div #targetElement></div>
// src/app/app.component.ts
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"],
})
export class AppComponent {
@ViewChild('targetElement') targetElement: targetElement;
}
Once we have a reference to the element, we can register an event listener with the addEventListener method.
// src/app/app.component.ts
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"],
})
export class AppComponent {
@ViewChild('targetElement') targetElement: ElementRef;
ngAfterViewInit(): void {
this.listenToAnimationStart();
this.listenToAnimationEnd();
this.listenToAnimationIteration();
}
ngOnDestroy(): void {
this.targetElement.nativeElement.removeEventListener('animationstart');
this.targetElement.nativeElement.removeEventListener('animationend');
this.targetElement.nativeElement.removeEventListener('animationiteration');
}
listenToAnimationStart(): void {
this.targetElement.nativeElement.addEventListener('animationstart', () => {
console.log('animation started');
})
}
listenToAnimationEnd(): void {
this.targetElement.nativeElement.addEventListener('animationend', () => {
console.log('animation ended');
})
}
listenToAnimationIteration(): void {
this.targetElement.nativeElement.addEventListener('animationiteration', () => {
console.log('animation iteration');
})
}
}
With this method, we also need to ensure listeners are removed when the component is destroyed, preventing memory leaks. The ngOnDestroy hook in the snippet above handles this.
Approach 2: Angular's Template Event Binding
Angular also allows handling these events directly in the template. This cuts down on boilerplate and removes the need for manual cleanup.
Let's adapt the previous approach. Instead of assigning an id, we enclose the event names in parentheses and bind them to methods that Angular invokes when the events fire.
<!-- src/app/app.component.html -->
<div
(animationstart)="onAnimationStart()"
(animationend)="onAnimationEnd()"
(animationiteration)="onAnimationInteration()"
></div>
In the component class, add the onAnimationStart, onAnimationEnd, and onAnimationIteration methods.
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"],
})
export class AppComponent {
onAnimationStart(): void {
console.log('animation started');
}
onAnimationEnd(): void {
console.log('animation ended');
}
onAnimationInteration(): void {
console.log('animation iteration');
}
}
Wrapping Up
Each approach has its trade-offs. The addEventListener path is more verbose, but it offers extra flexibility — we can pass configuration options that control when the listener fires, giving us finer control over the event phase. Combined with RxJS observables, it also opens the door to more advanced scenarios where we want to merge multiple event streams.
If you found this useful or have any questions, drop a comment or reach out on Twitter at @williamjuan27.
