DEV Community

Fazal Shah
Fazal Shah

Posted on

Lottie Animations in Angular: A Complete Guide

Angular's component-based architecture and dependency injection make Lottie integration clean and reusable. This guide covers everything from basic setup to lazy loading, services, and RxJS-driven control.


Before You Start

Open your animation files in IconKing first:

  • Preview colors, timing, and layers before integrating
  • Convert .json → .lottie for 75% smaller file size
  • Edit colors to match your Angular app's design tokens

Installation

npm install lottie-web
npm install --save-dev @types/lottie-web

# Or for dotLottie format
npm install @lottiefiles/dotlottie-web
Enter fullscreen mode Exit fullscreen mode

Basic Component (Vanilla lottie-web)

The most direct approach — create a reusable Angular component:

// src/app/components/lottie-animation/lottie-animation.component.ts
import {
  Component,
  Input,
  OnInit,
  OnDestroy,
  ElementRef,
  ViewChild,
  AfterViewInit
} from '@angular/core';
import lottie, { AnimationItem } from 'lottie-web';

@Component({
  selector: 'app-lottie-animation',
  template: `<div #container [style.width.px]="width" [style.height.px]="height"></div>`,
  standalone: true,
})
export class LottieAnimationComponent implements AfterViewInit, OnDestroy {
  @ViewChild('container') containerRef!: ElementRef<HTMLDivElement>;

  @Input() src = '';
  @Input() width = 200;
  @Input() height = 200;
  @Input() loop = true;
  @Input() autoplay = true;

  private anim: AnimationItem | null = null;

  ngAfterViewInit(): void {
    this.anim = lottie.loadAnimation({
      container: this.containerRef.nativeElement,
      renderer: 'svg',
      loop: this.loop,
      autoplay: this.autoplay,
      path: this.src,
    });
  }

  ngOnDestroy(): void {
    this.anim?.destroy();
  }

  play(): void { this.anim?.play(); }
  pause(): void { this.anim?.pause(); }
  stop(): void { this.anim?.stop(); }
}
Enter fullscreen mode Exit fullscreen mode
<!-- app.component.html -->
<app-lottie-animation
  src="/assets/animations/hero.json"
  [width]="400"
  [height]="400"
/>
Enter fullscreen mode Exit fullscreen mode

Place animation files in src/assets/animations/ — Angular copies assets/ to the build output.


Using ngx-lottie (Recommended Wrapper)

ngx-lottie is the maintained Angular-specific wrapper:

npm install ngx-lottie lottie-web
Enter fullscreen mode Exit fullscreen mode

App configuration (standalone API):

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideLottieOptions } from 'ngx-lottie';
import player from 'lottie-web';

export const appConfig: ApplicationConfig = {
  providers: [
    provideLottieOptions({
      player: () => player,
    }),
  ],
};
Enter fullscreen mode Exit fullscreen mode

Module-based setup:

// app.module.ts
import { LottieModule } from 'ngx-lottie';
import player from 'lottie-web';

export function playerFactory() {
  return player;
}

@NgModule({
  imports: [
    LottieModule.forRoot({ player: playerFactory }),
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Usage in templates:

<!-- Using object options -->
<ng-lottie
  [options]="lottieOptions"
  [width]="'300px'"
  [height]="'300px'"
  (animationCreated)="onAnimationCreated($event)"
/>
Enter fullscreen mode Exit fullscreen mode
import { Component } from '@angular/core';
import { AnimationOptions } from 'ngx-lottie';
import { AnimationItem } from 'lottie-web';

@Component({
  selector: 'app-hero',
  templateUrl: './hero.component.html',
})
export class HeroComponent {
  lottieOptions: AnimationOptions = {
    path: '/assets/animations/hero.json',
    loop: true,
    autoplay: true,
  };

  private animItem: AnimationItem | null = null;

  onAnimationCreated(anim: AnimationItem): void {
    this.animItem = anim;
  }

  play(): void { this.animItem?.play(); }
  pause(): void { this.animItem?.pause(); }
}
Enter fullscreen mode Exit fullscreen mode

Lazy Loading with loadAnimation

For large animations, load them on demand using Angular's HTTP client:

import { Component, OnInit, OnDestroy, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import lottie, { AnimationItem } from 'lottie-web';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-lazy-lottie',
  template: `<div #container [style.width.px]="width" [style.height.px]="height"></div>`,
})
export class LazyLottieComponent implements AfterViewInit, OnDestroy {
  @ViewChild('container') containerRef!: ElementRef;

  width = 300;
  height = 300;

  private anim: AnimationItem | null = null;
  private sub: Subscription | null = null;

  constructor(private http: HttpClient) {}

  ngAfterViewInit(): void {
    this.sub = this.http
      .get('/assets/animations/complex.json')
      .subscribe((animData) => {
        this.anim = lottie.loadAnimation({
          container: this.containerRef.nativeElement,
          renderer: 'svg',
          loop: true,
          autoplay: true,
          animationData: animData,
        });
      });
  }

  ngOnDestroy(): void {
    this.sub?.unsubscribe();
    this.anim?.destroy();
  }
}
Enter fullscreen mode Exit fullscreen mode

Angular Service for Animation Management

For apps with multiple animations, centralize control in a service:

// src/app/services/lottie.service.ts
import { Injectable } from '@angular/core';
import lottie, { AnimationItem } from 'lottie-web';

@Injectable({ providedIn: 'root' })
export class LottieService {
  private animations = new Map<string, AnimationItem>();

  register(id: string, anim: AnimationItem): void {
    this.animations.set(id, anim);
  }

  play(id: string): void { this.animations.get(id)?.play(); }
  pause(id: string): void { this.animations.get(id)?.pause(); }
  stop(id: string): void { this.animations.get(id)?.stop(); }

  setSpeed(id: string, speed: number): void {
    this.animations.get(id)?.setSpeed(speed);
  }

  destroy(id: string): void {
    this.animations.get(id)?.destroy();
    this.animations.delete(id);
  }

  destroyAll(): void {
    this.animations.forEach(anim => anim.destroy());
    this.animations.clear();
  }
}
Enter fullscreen mode Exit fullscreen mode
// In a component
@Component({ /* ... */ })
export class HeroComponent implements AfterViewInit, OnDestroy {
  @ViewChild('container') containerRef!: ElementRef;

  constructor(private lottieService: LottieService) {}

  ngAfterViewInit(): void {
    const anim = lottie.loadAnimation({
      container: this.containerRef.nativeElement,
      renderer: 'svg',
      loop: true,
      autoplay: true,
      path: '/assets/animations/hero.json',
    });
    this.lottieService.register('hero', anim);
  }

  ngOnDestroy(): void {
    this.lottieService.destroy('hero');
  }
}
Enter fullscreen mode Exit fullscreen mode

IntersectionObserver Directive

Create an Angular directive to pause animations when off-screen:

// src/app/directives/lottie-observe.directive.ts
import {
  Directive,
  ElementRef,
  Input,
  OnInit,
  OnDestroy
} from '@angular/core';
import { AnimationItem } from 'lottie-web';

@Directive({
  selector: '[appLottieObserve]',
  standalone: true,
})
export class LottieObserveDirective implements OnInit, OnDestroy {
  @Input() animItem: AnimationItem | null = null;

  private observer: IntersectionObserver | null = null;

  constructor(private el: ElementRef) {}

  ngOnInit(): void {
    this.observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          this.animItem?.play();
        } else {
          this.animItem?.pause();
        }
      },
      { threshold: 0.1 }
    );
    this.observer.observe(this.el.nativeElement);
  }

  ngOnDestroy(): void {
    this.observer?.disconnect();
  }
}
Enter fullscreen mode Exit fullscreen mode
<div
  #container
  appLottieObserve
  [animItem]="animItem"
  style="width: 300px; height: 300px"
></div>
Enter fullscreen mode Exit fullscreen mode

prefers-reduced-motion in Angular

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class MotionService {
  private mq = window.matchMedia('(prefers-reduced-motion: reduce)');

  get prefersReduced(): boolean {
    return this.mq.matches;
  }

  onChange(callback: (reduced: boolean) => void): void {
    this.mq.addEventListener('change', (e) => callback(e.matches));
  }
}
Enter fullscreen mode Exit fullscreen mode
// In component
constructor(private motionService: MotionService) {}

ngAfterViewInit(): void {
  const reduced = this.motionService.prefersReduced;

  this.anim = lottie.loadAnimation({
    /* ... */
    loop: !reduced,
    autoplay: !reduced,
  });

  if (reduced) {
    this.anim.addEventListener('DOMLoaded', () => {
      this.anim!.goToAndStop(this.anim!.totalFrames - 1, true);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

dotLottie in Angular

For .lottie format (75% smaller files):

npm install @lottiefiles/dotlottie-web
Enter fullscreen mode Exit fullscreen mode
import { Component, AfterViewInit, ViewChild, ElementRef, OnDestroy } from '@angular/core';
import { DotLottie } from '@lottiefiles/dotlottie-web';

@Component({
  selector: 'app-dot-lottie',
  template: `<canvas #canvas [style.width.px]="width" [style.height.px]="height"></canvas>`,
  standalone: true,
})
export class DotLottieComponent implements AfterViewInit, OnDestroy {
  @ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;

  width = 300;
  height = 300;

  private dotLottie: DotLottie | null = null;

  ngAfterViewInit(): void {
    this.dotLottie = new DotLottie({
      canvas: this.canvasRef.nativeElement,
      src: '/assets/animations/hero.lottie',
      loop: true,
      autoplay: true,
    });
  }

  ngOnDestroy(): void {
    this.dotLottie?.destroy();
  }
}
Enter fullscreen mode Exit fullscreen mode

Angular Signals Integration (Angular 17+)

Use Angular signals to reactively control animation state:

import { Component, signal, effect, AfterViewInit, ViewChild, ElementRef, OnDestroy } from '@angular/core';
import lottie, { AnimationItem } from 'lottie-web';

@Component({
  selector: 'app-reactive-lottie',
  template: `
    <div #container style="width: 300px; height: 300px"></div>
    <button (click)="togglePlay()">
      {{ isPlaying() ? 'Pause' : 'Play' }}
    </button>
  `,
  standalone: true,
})
export class ReactiveLottieComponent implements AfterViewInit, OnDestroy {
  @ViewChild('container') containerRef!: ElementRef;

  isPlaying = signal(true);
  speed = signal(1);

  private anim: AnimationItem | null = null;

  constructor() {
    effect(() => {
      if (this.anim) {
        this.isPlaying() ? this.anim.play() : this.anim.pause();
      }
    });

    effect(() => {
      this.anim?.setSpeed(this.speed());
    });
  }

  ngAfterViewInit(): void {
    this.anim = lottie.loadAnimation({
      container: this.containerRef.nativeElement,
      renderer: 'svg',
      loop: true,
      autoplay: true,
      path: '/assets/animations/hero.json',
    });
  }

  togglePlay(): void {
    this.isPlaying.set(!this.isPlaying());
  }

  ngOnDestroy(): void {
    this.anim?.destroy();
  }
}
Enter fullscreen mode Exit fullscreen mode

Performance Checklist for Angular

Check Solution
Large .json files Convert to .lottie at IconKing
Animations in module bundle Load via HttpClient at runtime
Off-screen animations running Use LottieObserveDirective
Memory leak on route change Always call anim.destroy() in ngOnDestroy
Multiple simultaneous animations Use canvas renderer instead of svg

Summary

  1. Use ngx-lottie for the cleanest Angular integration with proper typing
  2. Always call anim.destroy() in ngOnDestroy — route changes cause leaks
  3. For large files, use HttpClient to fetch at runtime instead of bundling
  4. Create a LottieService to manage multiple animations centrally
  5. Use IntersectionObserver directive to pause off-screen animations
  6. Convert to .lottie at IconKing for smaller files + better Lighthouse scores

Top comments (0)