Deferrable views with @defer

Angular · Advanced · 12 min

@defer cuts initial bundle size and speeds up first render with almost no code.

`@defer` lazy-loads a section of a template. The compiler moves the standalone components, directives and pipes used only inside the block into a separate chunk, which is downloaded and rendered when a trigger fires.

@defer (on viewport) { <app-comments /> }
@placeholder { <p>Comments</p> }
@loading (minimum 300ms) { <app-spinner /> }
@error { <p>Could not load comments</p> }

Triggers:

  • on idle (the default) — when the browser is idle.
  • on viewport — when the placeholder (or a referenced element) scrolls into view.
  • on interaction / on hover — when the user clicks/focuses or hovers the placeholder or a referenced element (on interaction(trigger) with #trigger).
  • on immediate, on timer(2s), and when condition() for custom logic.

Add prefetching to download early but render later: @defer (on interaction; prefetch on idle).

Sub-blocks: @placeholder shows before the trigger (with minimum to avoid flicker), @loading while the chunk loads (after and minimum parameters), @error if loading fails.

Rules: dependencies inside the block must be standalone and not referenced elsewhere in the same file (otherwise they are bundled eagerly). Triggers that need the DOM (viewport, interaction, hover) require a single root element in @placeholder or a reference.

During server-side rendering, @defer renders the placeholder; with incremental hydration it can render the real content on the server and hydrate it later.

Example

import { Component, signal } from '@angular/core';
import { Comments } from './comments';      // standalone, used only inside @defer
import { HeavyChart } from './heavy-chart';

@Component({
  selector: 'app-article',
  imports: [Comments, HeavyChart],
  template: `
    <article>...long text...</article>
    @defer (on viewport; prefetch on idle) {
      <app-comments />
    } @placeholder (minimum 200ms) {
      <p>Comments load when you scroll here</p>
    } @loading (after 100ms; minimum 300ms) {
      <p>Loading comments...</p>
    } @error {
      <p>Comments are unavailable</p>
    }
    <button #show>Show stats</button>
    @defer (on interaction(show)) { <app-heavy-chart /> }
    @defer (when isAdmin()) { <p>Admin tools loaded</p> }`,
})
export class Article {
  protected isAdmin = signal(false);
}

Remember

  • @defer (trigger) {} splits used components into a lazy chunk
  • Triggers: idle (default), viewport, interaction, hover, immediate, timer, when
  • prefetch on ... downloads early; @placeholder, @loading, @error handle states

Watch out for

  • Importing a deferred component somewhere else in the same file (or using it outside the block), which makes it load eagerly.

Check yourself

  1. A component is used inside @defer but also referenced in a ViewChild query in the same file. What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: angular.dev/guide/templates/defer