skip to content

Deferrable View Triggers

A @defer block fetches its dependencies as a separate chunk when a trigger fires: idle, viewport, interaction, hover, immediate, timer or a when condition. Interviewers ask about prefetch vs render.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Angular, which triggers can an @defer block use, and what happens when you write @defer with no trigger at all?

level: juniorimportance: must knowfreq 62%

answer

  1. six on-triggers plus a condition
  2. browser quiet time is the default
  3. idle, viewport, interaction, hover, immediate, timer
  4. when takes an expression, fires once

basics

~10 s

An Angular @defer block loads its content when an on trigger fires (idle, viewport, interaction, hover, immediate or timer) or a when condition turns truthy. With no trigger written, it defaults to on idle.

solid answer

~40 s

A `@defer` block keeps its components, directives and pipes out of the initial bundle and loads them when a trigger fires. The `on` triggers are `idle` (browser idle time, via `requestIdleCallback`), `viewport` (an element enters the viewport), `interaction` (click or keydown on an element), `hover` (mouse hover or focus on an element), `immediate` (right after the non-deferred content has rendered) and `timer(500ms)`. A `when` trigger takes a template expression and fires once it becomes truthy. Several triggers separated by `;` are OR-ed: the first one wins. If you write no main trigger at all, the compiler adds `on idle`. Once the content is swapped in, it stays; the block never goes back to its placeholder.

code

ts · 27 lines
ts
import { Component, signal } from '@angular/core';
import { Comments } from './comments';
import { Related } from './related';
import { AdminTools } from './admin-tools';

@Component({
  selector: 'app-article',
  imports: [Comments, Related, AdminTools],
  template: `
    @defer {
      <app-comments />
    }

    @defer (on viewport) {
      <app-related />
    } @placeholder {
      <section class="related-skeleton"></section>
    }

    @defer (when isAdmin()) {
      <app-admin-tools />
    }
  `,
})
export class Article {
  readonly isAdmin = signal(false);
}

go deeper

for a junior

Name the six on-triggers and the when condition, and state that a block with no trigger defaults to on idle.

for a middle

Explain that combined triggers are OR-ed, that element triggers watch the placeholder root by default, and that when is a one-time switch.

for a senior

Choose triggers per block from what the user will do on the page, and know triggers do not run during SSR and HMR fetches chunks eagerly.

for a principal

Set team guidance on which content may be deferred and with which trigger, so above-the-fold content is never deferred by habit.

## What a trigger is for An Angular **`@defer` block** (a *deferrable view*, introduced with the built-in control flow in **v17**) wraps a part of a template whose code does not need to ship with the first render. The compiler turns every standalone component, directive and pipe used only inside the block into a **dynamic `import()`**, so the bundler emits them as a separate chunk. Something then has to decide *when* that chunk is fetched and the block is rendered. That decision is the **trigger**. Until the trigger fires, the block shows its placeholder content (or nothing). When it fires, Angular fetches the chunk, then swaps the real content in. ## The `on` triggers | Trigger | Fires when | Needs an element? | |---|---|---| | `on idle` | the browser reports idle time (`requestIdleCallback`, with a `setTimeout` fallback) | no | | `on idle(500)` | idle time, but no later than the given timeout in ms (**v22**) | no | | `on viewport` | the watched element enters the viewport (IntersectionObserver) | yes | | `on interaction` | the user clicks or presses a key on the watched element | yes | | `on hover` | the pointer enters the watched element or it receives focus | yes | | `on immediate` | right after the non-deferred content of the view has rendered | no | | `on timer(2s)` | after the given duration, written in `ms` or `s` | no | The element-based triggers (`viewport`, `interaction`, `hover`) watch the placeholder's single root element by default, or an element named with a template reference variable, e.g. `on viewport(footer)`. ## The `when` trigger `when` takes an ordinary template expression: `@defer (when showDetails()) { ... }`. Angular re-evaluates it during change detection of the host view, and the first time it is **truthy** the block loads. It is a **one-time** switch: if the expression later becomes falsy again, the loaded content stays. A signal must be *called* in the expression; `when showDetails` passes the signal function itself, which is always truthy. ## Combining triggers - Separate triggers with a semicolon: `@defer (on viewport; on timer(5s))`. - Combined triggers are an **OR**: whichever fires first loads the block, and the others are cleaned up. - You can mix `on` and `when`: `@defer (on interaction; when isAdmin())`. - Each trigger kind may appear only once among the main triggers (and once among the prefetch triggers); the compiler rejects a duplicate such as two `on viewport` clauses. ## The default If a block has **no main trigger** — no `on` and no `when` — the compiler inserts `on idle` for you: ```html @defer { <app-comments /> } ``` behaves like `@defer (on idle)`. Note the exact rule: only *main* triggers count. A block that has only a `prefetch` clause still gets the implicit `on idle`, which is why the `deferTriggerMisconfiguration` extended diagnostic (NG8021) warns about `@defer (prefetch when flag())` on its own. ## What happens after the trigger 1. Angular cleans up the block's remaining triggers (listeners, observers, timers). 2. If the chunk has not been fetched yet, it starts the dynamic imports. 3. When all of them resolve, the main content is created and replaces the placeholder. The swap is **one-way**. A `@defer` block is not a toggle like `@if`; to hide the content again you wrap it in an `@if`. ## Where triggers do not run - During **server-side rendering**, triggers are not invoked; the server renders the placeholder and the client activates the triggers after hydration. - With **HMR** active in development, all `@defer` chunks are fetched eagerly, so network timing in `ng serve` is not what production does; rendering still waits for the trigger.

  • What does on idle(500) change compared with plain on idle?
    Since Angular v22, `on idle` accepts a timeout in milliseconds that is passed to `requestIdleCallback`. Plain `on idle` can wait a long time on a busy page; `on idle(500)` means the block loads when the browser is idle or after 500 ms, whichever comes first. It also works on prefetch: `prefetch on idle(500)`.
  • If you write @defer (on viewport; on timer(5s)), when does the block load?
    Whichever fires first. Triggers separated by semicolons are OR conditions: if the user scrolls the placeholder into view after two seconds, the viewport trigger loads it; if they never scroll, the timer loads it at five seconds. After one fires, Angular removes the other trigger's registration.
  • Why does @defer (on viewport) without a @placeholder block fail to compile?
    A target-less `viewport`, `interaction` or `hover` trigger watches the placeholder's root element. Without a `@placeholder`, there is nothing to watch, so the compiler reports that a trigger with no target needs a `@placeholder` block. Either add a placeholder with exactly one root element or pass a template reference, as in `on viewport(anchor)`.

saying these in an interview costs you the question

  • A @defer block with no trigger loads immediately on first render.
  • Multiple triggers must all fire before the block loads.
  • A when condition that turns false hides the content again.
  • on viewport watches the deferred content itself entering the viewport.
  • Triggers also run on the server during SSR.
open as a page

In an Angular @defer block, what is the difference between the main trigger and a prefetch trigger, and why combine them?

level: middleimportance: must knowfreq 52%

basics

~20 s

In Angular @defer, the main trigger decides when the deferred content is rendered; a prefetch trigger only downloads its chunk earlier. Combining them, e.g. on interaction; prefetch on idle, hides network latency without rendering too soon.

open as a page

In Angular, how do the @defer viewport, interaction and hover triggers decide which element to watch, and what rules apply to it?

level: middleimportance: should knowfreq 38%

basics

~20 s

Angular's element-based @defer triggers watch the @placeholder's single root element by default, or an element named by a template reference variable in the same template, reachable from the block. Missing or unreachable targets are compile errors.

open as a page

In Angular, how does @defer (when expr) evaluate its condition, and how does it differ from wrapping content in @if?

level: middleimportance: should knowfreq 36%

basics

~20 s

Angular evaluates a @defer when expression during change detection of its host view; the first truthy value loads and renders the block, permanently. @if toggles content both ways but its dependencies ship in the eager bundle.

open as a page

An Angular dashboard defers a heavy chart below the fold with @defer (on viewport), and fast scrollers see an empty placeholder for a second; how do you fix it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Angular's on viewport starts the download only when the placeholder intersects, so the chunk fetch is on the critical path. Start it earlier: add prefetch on idle, or a viewport rootMargin, and give the placeholder the chart's size.

open as a page