In Angular, how does @defer (when expr) evaluate its condition, and how does it differ from wrapping content in @if?
answer
- checked during change detection
- truthy once, never reverts
- call the signal
- @if destroys, @defer keeps
basics
~20 sAngular 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.
solid answer
~40 s`@defer (when expr)` compiles to a binding that Angular checks each time the host view is refreshed. While the value is falsy, the placeholder shows; the first time it is truthy, Angular fetches the chunk and renders the content, and from then on the expression no longer matters. That is the difference from `@if`: `@if` creates and destroys content every time its condition flips, and the components it uses are bundled eagerly. Use `@defer (when …)` to load code on a condition, and nest an `@if` inside it if the content must also hide again. Two traps: call signals (`when ready()`; a bare signal function is always truthy), and remember that a plain field changed outside Angular's notice does not refresh an `OnPush` view, so the condition is never re-read.
code
ts · 19 linesimport { Component, signal } from '@angular/core';
import { ReportViewer } from './report-viewer';
@Component({
selector: 'app-reports',
imports: [ReportViewer],
template: `
<button type="button" (click)="panelOpen.set(!panelOpen())">Toggle report</button>
@defer (when panelOpen()) {
@if (panelOpen()) {
<app-report-viewer />
}
}
`,
})
export class Reports {
readonly panelOpen = signal(false);
}go deeper
Remember that when takes an expression and loads once it is truthy, and that the content then stays.
Explain that the expression is a binding read during change detection, contrast it with @if, and nest the two when you need both.
Feed when from signals so OnPush and zoneless views re-read it, and catch uninvoked signals and prefetch-only conditions in review.
Decide which feature flags and role checks gate code download versus only visibility, since only the former saves bytes.
## What `when` is Besides the event-like `on` triggers, an Angular **`@defer` block** accepts a **`when`** trigger: an ordinary template expression. ```html @defer (when reportRequested()) { <app-report-viewer /> } @placeholder { <p>No report yet.</p> } ``` The compiler turns the expression into a **binding** in the template's update code, like any `{{ }}` or `[prop]` binding. It is therefore evaluated **during change detection of the host view**, not by a timer or an observer. ## What happens on each evaluation 1. **Falsy, nothing rendered yet** — Angular renders the placeholder (if there is one) and waits. 2. **First truthy value** — the block triggers: other triggers are cleaned up, the dependencies are fetched (or reused if prefetched), and the main content replaces the placeholder. 3. **Any later value** — ignored. The block has already triggered, and a falsy value does **not** bring the placeholder back. That third point is the one interviewers probe: `when` is a **one-time** switch. ## `@defer (when …)` versus `@if` | | `@defer (when cond)` | `@if (cond)` | |---|---|---| | Code of the content | separate lazy chunk | in the component's eager bundle | | Condition goes true | fetch, then render | render at once | | Condition goes false again | content stays | content destroyed | | Placeholder, loading, error states | yes | `@else` only | They compose. To load code lazily *and* toggle visibility, nest them: ```html @defer (when panelOpen()) { @if (panelOpen()) { <app-report-viewer /> } } ``` The first `true` downloads the code; after that the `@if` shows and hides the already-loaded component. ## Traps - **Uninvoked signals.** `when reportRequested` passes the signal *function*, which is always truthy, so the block loads on the first check. Write `when reportRequested()`. - **Values Angular never re-reads.** Since the expression is re-read only when the host view is checked, a plain class field mutated from a callback Angular is not told about may never be seen. Components default to **`OnPush`** since **v22** and applications are **zoneless** by default since **v21**, so the reliable input is a **signal** (reading it in the template marks the view for refresh when it changes) or an input. - **Prefetch-only conditions.** `@defer (prefetch when cond())` with no main trigger still gets the implicit `on idle`, so it loads regardless of `cond()`; the `deferTriggerMisconfiguration` diagnostic (NG8021) warns about it. ## Combining `when` with other triggers - `@defer (on viewport; when isAdmin())` — whichever becomes true first; this is an **OR**, not an AND. If you need "admin *and* visible", the `@defer` must be nested inside an `@if (isAdmin())`, or the condition must fold both facts into one expression. - `@defer (on interaction; prefetch when likelyToOpen())` — render on click, fetch the code as soon as a heuristic says it will probably be needed. ## Where `when` fits `when` is the right trigger when the loading decision is **application state** rather than a browser event: a feature flag, a role, a completed wizard step, a tab the user selected. For browser events such as scroll position, clicks or idle time, the dedicated `on` triggers are cheaper and do not depend on change detection.
- How would you express 'load only when the user is an admin and the section is visible'?Triggers in one `@defer` are OR conditions, so `on viewport; when isAdmin()` loads for non-admins who scroll too. Wrap the block instead: `@if (isAdmin()) { @defer (on viewport) { … } }`. Non-admins never create the defer block, so no trigger is registered and nothing is fetched.
- Why might @defer (when ready) load immediately on first render?If `ready` is a signal, `when ready` evaluates the signal function object, not its value, and a function is always truthy. The block triggers on the first check. Call it: `when ready()`.
saying these in an interview costs you the question
- A when condition that becomes false again restores the placeholder.
- @defer (when cond) and @if (cond) bundle their content the same way.
- The when expression is polled on a timer.
- on viewport; when isAdmin() loads only for admins who scroll.
- Passing a signal without calling it works because Angular unwraps it.