AngularAtlased. 22.2

Ch. 16Advanced UI & performance70 / 75

Modern animations & View Transitions

animate.enter and animate.leave instead of the deprecated @angular/animations, CSS transitions and View Transitions on navigation.

v22 ~0 min read Official docs

Since v20.2, Angular has two new instructions in @angular/core: animate.enter and animate.leave. You write the animation in CSS, like any other animation. Angular does the part CSS can’t do on its own: it adds a class when the element enters the DOM and, on exit, delays removing the element until the animation finishes. In the same version, the @angular/animations package (trigger, state, transition) was deprecated.

For transitions between pages, or between two states of the same screen, there is the View Transitions API. It is a browser API, integrated into the router with withViewTransitions().

What changed
The new API no longer needs a separate package, provideAnimations() or a DSL in TypeScript. Animations are regular CSS, accelerated by the browser, and animate.leave solves the hard part, which is animating an element that is about to disappear. The v22 documentation explicitly recommends animate.enter / animate.leave for all new code.

animate.enter and animate.leave

animate.enter="enter" adds the class enter when the element is inserted (an @if becomes true, a new element appears in an @for) and removes it when the CSS animation ends. animate.leave="leave" sets the class on removal and keeps the element in the DOM until the animation ends. They are not directives: they are compiler instructions, so they don’t need to be imported and can also be used as host bindings.

@for (item of items(); track item.id) {
  <li animate.enter="enter" animate.leave="leave">{{ item.label }}</li>
}
LiveAnimated list (real animate.enter / animate.leave)
in list: 4 · elements in DOM: 4
  • Apple
  • Pear
  • Plum
  • Cherry
Remove the first element and watch “elements in DOM”: it keeps one extra while the exit animation runs (600 ms), then goes down. Add a few elements to see the entry animation.

Keyframes or transition

With @keyframes it is simple: the class contains the animation, and Angular waits for its end event. With transition, the documentation points to a detail: the enter class represents the final state. You give the starting state with @starting-style, otherwise there is no “from” for the transition to start from.

/* With transition instead of keyframes: the class is the FINAL state, and @starting-style is the starting state */
.panel { transition: opacity 250ms, transform 250ms; }
.panel-enter {
  opacity: 1;
  transform: none;
  @starting-style {
    opacity: 0;
    transform: translateY(12px);
  }
}

The function form: full control

(animate.enter)="fn($event)" and (animate.leave)="fn($event)" call a method with an AnimationCallbackEvent: target (the element) and animationComplete(). This is how you integrate the Web Animations API or libraries such as GSAP. On leave, the call to animationComplete() is mandatory. If it is missing, Angular removes the element only after MAX_ANIMATION_TIMEOUT, which is 4 seconds by default.

import { AnimationCallbackEvent, Component, signal } from '@angular/core';

@Component({
  selector: 'app-toasts',
  template: `
    @for (t of toasts(); track t.id) {
      <div class="toast" (animate.enter)="slideIn($event)" (animate.leave)="slideOut($event)">{{ t.text }}</div>
    }
  `,
})
export class Toasts {
  readonly toasts = signal<Toast[]>([]);

  slideIn(event: AnimationCallbackEvent): void {
    event.target.animate([{ opacity: 0, transform: 'translateX(40px)' }, { opacity: 1, transform: 'none' }], 250);
  }

  slideOut(event: AnimationCallbackEvent): void {
    const animation = event.target.animate([{ opacity: 1 }, { opacity: 0, transform: 'translateX(40px)' }], 300);
    // REQUIRED on leave: otherwise Angular removes the element only after MAX_ANIMATION_TIMEOUT (4 s by default)
    animation.finished.then(() => event.animationComplete());
  }
}
LiveAnimated notifications from TypeScript (Web Animations API)
closes by itself after 2.5 s or on click
  1. The hook log appears here.
Show a few notifications and close them with a click. The log shows the order: leave starts the animation, and the element disappears only after animationComplete().

Migrating from @angular/animations

Most animations written with trigger('fade', [transition(':enter', …)]) translate directly into two CSS classes and animate.enter / animate.leave. States (state('open')) become regular CSS classes, bound with [class.open]. The two systems can coexist in an application, but not in the same component: the documentation says the result would be classes that stay on elements, or nodes that are never removed. The same applies to content projection between components that use different systems.

// BEFORE — @angular/animations (deprecated since v20.2)
@Component({
  animations: [
    trigger('fade', [
      transition(':enter', [style({ opacity: 0 }), animate('200ms', style({ opacity: 1 }))]),
      transition(':leave', [animate('200ms', style({ opacity: 0 }))]),
    ]),
  ],
  template: `@if (open()) { <div @fade>…</div> }`,
})

// AFTER — CSS + animate.enter / animate.leave, with no package and no provideAnimations()
@Component({
  template: `@if (open()) { <div animate.enter="fade-in" animate.leave="fade-out">…</div> }`,
  styles: `
    .fade-in  { animation: fade 200ms; }
    .fade-out { animation: fade 200ms reverse forwards; }
    @keyframes fade { from { opacity: 0; } }
  `,
})

View Transitions

The browser takes a snapshot of the page, you change the DOM, then the browser animates the difference: by default a cross-fade, and elements with the same view-transition-name “transform” into one another (position, size). In the router you enable this with withViewTransitions(). The function is marked developer preview in the router’s types and has the options skipInitialTransition and onViewTransitionCreated. Outside the router you call document.startViewTransition() directly. In a zoneless application you must update the DOM synchronously, in the callback.

import { provideRouter, withViewTransitions } from '@angular/router';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withViewTransitions({
        skipInitialTransition: true, // no animation on the first load
        onViewTransitionCreated: ({ transition }) => {
          // runs in an injection context; you can skip certain navigations:
          // if (...) transition.skipTransition();
        },
      }),
    ),
  ],
};
LiveGrid ↔ detail with the View Transitions API

Your browser doesn’t support View Transitions: the change happens instantly (fallback).

Click a card: it “grows” into the detail panel, because both have the same view-transition-name. Click the detail to go back. In browsers without support, the change is instant.

Performance and accessibility

  • Animate transform and opacity: the compositor handles them, without layout. width, height and top recalculate the layout on every frame.
  • Short durations for UI (150–300 ms). Long animations make the interface feel slow.
  • Respect prefers-reduced-motion. This site does it globally; that is why, with “reduce motion” enabled in the system, the demos above are almost instant.
@media (prefers-reduced-motion: reduce) {
  .enter, .leave { animation-duration: 1ms; }
  ::view-transition-group(*) { animation: none; }
}

Items entering and leaving a list are animated with animate.enter on each @for row (see Control flow: @if, @for, @switch). On very long lists, animate only what is on screen: Huge lists: virtual scroll. What an animation costs and how to measure it, in Performance — a checklist.

When YES, when NO

Use
  • animate.enter / animate.leave for elements that appear and disappear: lists, toasts, panels, dialogs.
  • The function form when you need JavaScript: computed animations, GSAP, sequences.
  • View Transitions for navigations and layout changes (list → detail), with an instant fallback.
  • Plain CSS (:hover, transition) for everything that doesn’t involve insertion or removal.
Avoid
  • @angular/animations in new code: it is deprecated. Keep it only where it already exists, until you migrate.
  • Animations on layout properties in large lists: they stutter.
  • Animations that block interaction: the user waits for them to finish before being able to click.
  • View Transitions on every small state change: it becomes tiring and uses resources.

Common pitfalls

  • animationComplete() forgotten in (animate.leave): the element stays visible for another 4 seconds.
  • transition without @starting-style on enter: nothing animates, because there is no starting state.
  • animate.leave in a child component when the parent is removed. The documentation specifies that the leave animations of children only run for elements in the same template. Put animate.leave on the child’s host element, in the parent’s template.
  • The two systems in the same component (or through content projection): undefined behavior.
  • startViewTransition without a synchronous update in a zoneless application: the browser takes the snapshot of the old state twice and nothing animates.

Cheat sheet

ENTER/LEAVE (since v20.2, in @angular/core — no separate package)
  animate.enter="class"            the class is added on insertion and removed at the end of the animation
  animate.leave="class"            the element stays in the DOM until the animation ends
  [animate.enter]="expr"           string, array of classes, signal
  (animate.leave)="fn($event)"     AnimationCallbackEvent: target + animationComplete() — REQUIRED on leave
  MAX_ANIMATION_TIMEOUT            how long Angular waits for an unfinished leave (default 4 s)
CSS
  keyframes: the class contains the animation · transition: class = final state + @starting-style = start
  animate transform and opacity (compositor), not width/height/top (layout)
  prefers-reduced-motion — respect it
VIEW TRANSITIONS
  provideRouter(routes, withViewTransitions({ skipInitialTransition, onViewTransitionCreated }))  — developer preview
  view-transition-name: x  ·  ::view-transition-old/new(x)  ·  document.startViewTransition(cb)
@angular/animations  deprecated since v20.2 — only for existing code; don't mix it with animate.* in the same component

Ch. 16 · 0 of 4 complete

Finished this lesson?

Mark it and it shows up on your map: in the contents and on the home page.

Marks itself as done once you have read the whole lesson.

  1. Web Workers
  2. Huge lists: virtual scroll
  3. Modern animations & View Transitions
  4. Memory leaks: finding and fixing them
See your whole map

All 75 pages · type to filter

  • 01What's new in Angular 21 & 22All the important changes in v21 and v22, briefly, with a link to the page that explains each one.Start
  • 01Your first app, step by stepA reading list built from an empty component to a finished app — signals, @for, inputs and outputs, a service and a form, one step at a time.Start
  • 01Quick decisions“Where do I put this?”, “Signal or Observable?”, “Shared or feature?” — short answers to everyday questions.Start
  • 01Angular interview questions60 real interview questions for junior, mid and senior, with the answer to give, the follow-up and the red flags. Plus a mock interview.Start
  • 02Standalone & bootstrapHow an app starts without NgModule: bootstrapApplication, app.config.ts, imports in the component.Modern fundamentals
  • 02Data binding & eventsInterpolation, property and attribute bindings, events with $event and key filters, and how [( )] is built — what goes into the DOM and what comes back.Modern fundamentals
  • 02Control flow: @if, @for, @switchThe block syntax that replaces *ngIf/*ngFor/ngSwitch, with track, @empty and exhaustive checks.Modern fundamentals
  • 02@let and the new template expressionsLocal variables with @let, arrow functions, spread, regex, instanceof, comments inside tags.Modern fundamentals
  • 02@defer — lazy loading in templatesLoad heavy components only when needed: triggers, prefetch, placeholder, loading, error.Modern fundamentals
  • 02Class & style bindingsWhy [class.x] and [style.x] replaced ngClass and ngStyle (with automatic migration from v21).Modern fundamentals
  • 02Component styles & encapsulation:host, the three encapsulation modes, theming with CSS variables, and why ::ng-deep is a last resort.Modern fundamentals
  • 02Pipes: when and howPure vs impure pipes, custom pipes, and how you choose between a pipe, computed() and a method.Modern fundamentals
  • 03signal() and computed()The reactive primitives: writable state, derived state, equality, untracked and immutability rules.Signals & reactivity
  • 03effect() — when YES, when NOSide effects synchronised with signals: localStorage, logging, imperative APIs. And their traps.Signals & reactivity
  • 03linkedSignal() — editable derived stateA signal that resets when its source changes, but that you can also set manually.Signals & reactivity
  • 03resource, rxResource, httpResourceAsync data as signals: status, value, error, reload, automatic cancellation. Stable since v22.Signals & reactivity
  • 03debounced() — delayed signalsThe “debounce” version of a signal, without RxJS. Perfect for search.Signals & reactivity
  • 03Lists: search, filter, sort, paginateA chain of computed signals that recalculates only what changed, with the filters kept in the URL so a link reopens the same view.Signals & reactivity
  • 03Observables from zeroWhat an Observable is, subscribe and unsubscribe, the operators you use every week, cold vs hot — and when a signal is enough.Signals & reactivity
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — and when RxJS remains the right choice.Signals & reactivity
  • 04input() and output()Inputs as signals (required, transform, alias) and typed outputs. Data down, events up.Components: communication
  • 04model() — two-way bindingBanana-in-a-box [( )] with a single API. When to use it, and when input + output is better.Components: communication
  • 04viewChild & contentChildQueries as signals: viewChild, viewChildren, contentChild, contentChildren, the read option.Components: communication
  • 04Lifecycle & DestroyRefWhich hooks still matter in the signals era, afterNextRender, DestroyRef and typed ngOnChanges.Components: communication
  • 04Content projectionng-content with select, multiple slots, default content and the trap of conditional projection.Components: communication
  • 04ng-template & ngTemplateOutletTemplates as values: render props in Angular, typed context and customising components.Components: communication
  • 05Smart vs DumbContainer vs presentational: who holds the state, who only displays, and how to split them properly.Component design
  • 05Generic components <T>A correctly typed DataTable<T> or Select<T>, with templates for cells.Component design
  • 05Dynamic componentsNgComponentOutlet, createComponent with inputBinding/outputBinding, and a dashboard built from config.Component design
  • 05Directives & host compositionAttribute directives, the host: {} metadata and the Directive Composition API (hostDirectives).Component design
  • 05Custom form controlsFormValueControl (Signal Forms) vs ControlValueAccessor: a star rating, in both variants.Component design
  • 05Change detectionOnPush is the default from v22, zone.js is gone from v21. What triggers rendering and how not to get burned.Component design
  • 06inject() & @Service()The new @Service() decorator from v22, inject() vs the constructor, and the injection context.Dependency Injection
  • 06Providers & the injector hierarchyRoot vs route vs component, useClass/useValue/useFactory/useExisting, and the optional/self/skipSelf modifiers.Dependency Injection
  • 06InjectionToken & provideX()Typed configuration, provideFeature() functions, makeEnvironmentProviders and initializers.Dependency Injection
  • 06injectAsync() — lazy servicesLoad a heavy service only when you need it, with prefetch on idle.Dependency Injection
  • 07Folder structurecore / shared / features, what goes into each, and what a feature looks like inside.Application architecture
  • 07Shared vs featureWhen to move a component into shared and (especially) when NOT to. A concrete checklist.Application architecture
  • 07Interfaces & modelsInline interface or separate file? DTO vs domain model, type vs interface, where to put them.Application architecture
  • 07State management with signalsSimple signal stores, facades, local vs global state, and when you need NgRx.Application architecture
  • 07Style guide & namingThe current official conventions: file names without suffixes, protected, readonly, member order.Application architecture
  • 07Barrels, aliases & boundariesindex.ts, path aliases, a feature’s public API and dependency rules that can be checked automatically.Application architecture
  • 07Errors: @boundary & ErrorHandlerThe new @boundary block from v22.2, the global ErrorHandler, HTTP errors and errors from resource.Application architecture
  • 08Modern routing & lazy loadingloadComponent, child routes, parameters as inputs and parameter inheritance (the new default in v22).Routing
  • 08Guards & resolverscanActivate, canMatch, canDeactivate and resolve as functions, with redirects via UrlTree / RedirectCommand.Routing
  • 09Signal Forms — the basicsForms built on signals: form(), [formField], schema-based validation. Stable since v22.Forms
  • 09Signal Forms — advancedCustom and cross-field validation, async/HTTP with debounce, dynamic lists, disabled/hidden, reusable schemas.Forms
  • 09Typed Reactive FormsTyped FormGroup, nonNullable, FormArray — and how they coexist with Signal Forms.Forms
  • 10HttpClient, interceptors & the API layerHttpClient without setup (v21), Fetch by default (v22), functional interceptors and an API layer with DTO mapping.HTTP & data
  • 11Performance — a checklistWhat really matters: OnPush + signals, track, @defer, lazy routes, images, bundles and hydration.Quality & performance
  • 11Testing with VitestVitest is the default runner from v21. TestBed, components with signals, services, HTTP and harnesses.Quality & performance
  • 11DevTools: look inside your appChrome DevTools and Angular DevTools step by step: what each panel shows, with exercises that work on this site.Quality & performance
  • 11Angular AriaHeadless, accessible components that you style however you like: tabs, accordion, menu, listbox...Quality & performance
  • 12SSR, prerender & render modesWhen to render on the server, when at build time and when only in the browser: RenderMode per route, app.routes.server.ts and the real costs.SSR & hydration
  • 12Hydration: event replay & incrementalHow the browser takes over server HTML without re-rendering it: full hydration, event replay, @defer (hydrate on …) and mismatch errors.SSR & hydration
  • 12Server-compatible codewindow doesn’t exist on the server: PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, the HTTP cache and PendingTasks.SSR & hydration
  • 13Micro frontendsWhen it is worth splitting an app into several deployments and when it is just complexity: Native Federation, Module Federation, iframes, Web Components.Architecture at scale
  • 13Web Components with Angular ElementscreateCustomElement: an Angular component used as an HTML tag anywhere, plus communication between apps through events and BroadcastChannel.Architecture at scale
  • 13Monorepo, libraries & boundariesA workspace with several apps and libraries: ng generate library, Nx, secondary entry points, publishing and dependency rules.Architecture at scale
  • 13Hexagonal architecture (ports & adapters)A domain without Angular, ports as abstract classes and interchangeable adapters: HTTP, localStorage or in-memory, without touching the UI.Architecture at scale
  • 14Signals under the hoodThe reactive graph: producers and consumers, push-pull, why computed is lazy and glitch-free, the diamond problem and when effects run.Advanced reactivity
  • 14Advanced RxJS: higher-order operatorsswitchMap, mergeMap, concatMap and exhaustMap on a live diagram, plus retry with backoff, shareReplay and how to avoid leaks.Advanced reactivity
  • 14NgRx SignalStoreLarge-scale state management on signals: withState, withComputed, withMethods, rxMethod, entities and hooks — and when a simple store is enough.Advanced reactivity
  • 14State machines with signalsNo more impossible states: a typed machine on signals, with transitions, guards and a live state diagram.Advanced reactivity
  • 15WebSockets: real time that survives the networkA typed socket as signals: reconnect with backoff and jitter, heartbeat, an outbox while offline, and SSR safety.Real time & offline
  • 15Optimistic updates & rollbackAn instant UI that stays honest: snapshot and rollback, out-of-order responses, idempotency keys.Real time & offline
  • 15Offline-first: Service Worker & cachingThe Angular Service Worker: caching strategies, update prompts and an outbox for changes made offline.Real time & offline
  • 16Web WorkersHeavy computation without blocking the UI: a real worker in the page, message passing and when it is not worth it.Advanced UI & performance
  • 16Huge lists: virtual scroll100,000 rows without lag: CDK virtual scroll, itemSize, trackBy and the alternatives (pagination, infinite scroll).Advanced UI & performance
  • 16Modern animations & View Transitionsanimate.enter and animate.leave instead of the deprecated @angular/animations, CSS transitions and View Transitions on navigation.Advanced UI & performance
  • 16Memory leaks: finding and fixing themSubscriptions, timers and listeners that outlive their component — how to spot them in DevTools and close them.Advanced UI & performance
  • 17Security: XSS, sanitisation & CSPWhat Angular does automatically against XSS, when bypassSecurityTrust becomes dangerous, plus CSP with nonce, Trusted Types and XSRF.Security & globalisation
  • 17Authentication: tokens & refreshAn interceptor that attaches the token, refreshes once on 401 and replays the queued requests — plus where you keep the token.Security & globalisation
  • 17OAuth 2.1 & OpenID Connect with PKCEThe authorization code flow with PKCE, step by step: verifier and challenge, state and nonce, the ID token, and why a BFF is safer.Security & globalisation
  • 17Internationalisation (i18n)$localize, the i18n attribute, ICU plural/select, a build per language vs runtime translations, and locale-based formatting.Security & globalisation