skip to content

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%

answer

  1. download versus display
  2. two clocks on one block
  3. prefetch keyword after a semicolon
  4. on interaction; prefetch on idle

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.

solid answer

~40 s

A `@defer` block has two independent timings. The **main trigger** (`on …` or `when …`) decides when the content is shown, and if the chunk is not there yet it also starts the download. A **prefetch trigger**, written after a semicolon with the `prefetch` keyword, only starts the download — nothing renders. The usual pairing is `@defer (on interaction; prefetch on idle)`: the code arrives during idle time, so when the user clicks, the content appears with no network wait. If the main trigger fires before prefetching finishes, Angular reuses the in-flight load instead of fetching again. Prefetch accepts the same triggers, including `prefetch on idle(500)` and `prefetch when expr`.

code

ts · 15 lines
ts
import { Component } from '@angular/core';
import { FiltersPanel } from './filters-panel';

@Component({
  selector: 'app-search-page',
  imports: [FiltersPanel],
  template: `
    <button #filtersBtn type="button">Filters</button>

    @defer (on interaction(filtersBtn); prefetch on hover(filtersBtn)) {
      <app-filters-panel />
    }
  `,
})
export class SearchPage {}

go deeper

for a junior

Remember the prefetch keyword and the semicolon, and that prefetch downloads while the main trigger renders.

for a middle

Explain the shared loading state: prefetch then main reuses the load, main during prefetch waits on it, and nothing is fetched twice.

for a senior

Pick prefetch signals that predict the main trigger, like hover before click, and use NG8021 to catch pairs that never help.

for a principal

Weigh bandwidth on low-end devices against click latency when setting prefetch defaults for a large app.

## Two separate questions: when to download, when to show Every Angular **`@defer` block** compiles to a set of **dynamic imports** for the standalone components, directives and pipes used only inside it. Two things can happen to that block, and Angular lets you time them separately: - **Fetching** the JavaScript (and component CSS) for the deferred dependencies. - **Rendering** the main content, replacing the placeholder. The **main trigger** controls rendering. The **prefetch trigger** controls only fetching. ## The main trigger alone With just a main trigger, fetching and rendering start at the same moment: ```html @defer (on interaction) { <app-filters-panel /> } @placeholder { <button type="button">Show filters</button> } ``` The user clicks, Angular starts the import, the `@loading` state (if any) shows while the chunk is in flight, and the panel renders when it arrives. On a slow connection that is a visible wait after the click. ## Adding a prefetch trigger ```html @defer (on interaction; prefetch on idle) { <app-filters-panel /> } @placeholder { <button type="button">Show filters</button> } ``` Now the chunk downloads during browser idle time after the page settles. When the user clicks, the dependencies are already loaded and the panel renders at once. The syntax rules: - The prefetch clause uses the **`prefetch`** keyword and is separated from the main trigger with **`;`**. - It accepts the same trigger kinds as the main clause: `prefetch on idle`, `prefetch on idle(500)` (timeout since **v22**), `prefetch on viewport`, `prefetch on hover(ref)`, `prefetch on timer(1s)`, `prefetch on immediate`, `prefetch when expr`. ## How the two interact at runtime Angular keeps one loading state per block: not started, in progress, complete or failed. | Situation | What happens | |---|---| | Prefetch fires first | download starts; prefetch triggers are cleaned up; nothing renders | | Main fires after prefetch completed | content renders immediately from loaded code | | Main fires while prefetch in flight | Angular shows the loading state and waits for the same load; no second fetch | | Main fires before prefetch ever fired | main starts the download itself; all triggers are cleaned up | | The load failed | the block shows its error state | So prefetch can only make rendering faster; it never delays it and never causes a duplicate download. ## Choosing a pair Good pairs make the prefetch trigger fire **earlier** than the main one, on a signal that the user is likely to need the content: 1. `on interaction; prefetch on idle` — a dialog or panel behind a button. 2. `on interaction(openBtn); prefetch on hover(openBtn)` — hovering or focusing the button predicts the click. 3. `on viewport; prefetch on idle` — content further down that should render the moment it scrolls in. 4. `on timer(5s); prefetch on timer(2s)` — a delayed widget whose code is fetched a little ahead. ## Pairs the compiler warns about Since **v21**, the `deferTriggerMisconfiguration` extended diagnostic (**NG8021**, needs `strictTemplates`) flags pairs that do nothing useful: - `on immediate` with any prefetch: the prefetch can never run first. - A prefetch timer that is not shorter than the main timer. - Identical main and prefetch triggers, e.g. `on viewport; prefetch on viewport`. - A prefetch with **no main trigger**, e.g. `@defer (prefetch when flag())`. The block still gets the implicit `on idle` main trigger, so it may load and render during idle time, long before `flag()` is true. ## What prefetch does not do - It does not render anything, so it does not change layout or run component code. - It does not prefetch the `@placeholder`, `@loading` or `@error` dependencies; those are eagerly bundled anyway. - It does not affect server rendering: prefetch triggers are active only on the client.

  • What happens if the main trigger fires while the prefetch download is still in flight?
    Angular sees the block's loading state is already in progress, renders the `@loading` block if one exists, and waits on the same load. When it resolves, the main content renders. No second request is made, so an early prefetch can only shorten the wait.
  • Why is @defer (on immediate; prefetch on idle) pointless?
    `on immediate` starts loading right after the non-deferred content renders, which is before any idle period. The prefetch can never fire first, so it adds nothing. The `deferTriggerMisconfiguration` diagnostic (NG8021) flags this pair; remove the prefetch clause.
  • Does @defer (prefetch when userIsPremium()) load only for premium users?
    No. With no main trigger the compiler adds `on idle`, so the block loads and renders during idle time for everyone. To gate on the condition, make it the main trigger (`when userIsPremium()`) or add an explicit main trigger such as `on interaction`.

A kitchen that starts cooking a popular dish when you sit down (prefetch) but brings it to the table only when you order (main trigger). If you order before it is ready, you wait for the same pan; nobody starts a second one.

saying these in an interview costs you the question

  • Prefetch renders the content early but hides it until the main trigger.
  • If both triggers fire, the chunk is downloaded twice.
  • A block with only a prefetch clause waits for that prefetch condition to load.
  • Prefetching also loads the placeholder and loading block dependencies.
  • Prefetch only supports on idle.