@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), andwhen 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,@errorhandle 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.