Angular @defer Deferrable Views
- Last updated
- Prerequisites:
- Angular component and standalone imports
- Basic awareness of route-level lazy loading
- angular
- deferrable-views
- performance
- lazy-loading
- bundle-size
Read at your depth
The practical view
@defer lets you lazy-load any block of template plus its dependencies: @defer (on viewport) { <app-chart /> } @placeholder { <div>Loading chart…</div> } @loading (minimum 500ms) { <p>Fetching…</p> } @error { <p>Failed to load.</p> }. The triggers are on viewport, on idle, on interaction, on hover, on timer(5s), on immediate, or a custom when(condition). Each state block can also be combined. The key practical win: the component inside @defer, and everything it imports, is split into a separate chunk fetched only when the trigger fires — so the initial bundle and first-paint cost shrink without restructuring your module graph.
The same idea in other frameworks
const Chart = lazy(() => import('./Chart'));
<Suspense fallback={<div>Loading…</div>}><Chart /></Suspense>@defer (on viewport) {
<app-chart />
} @placeholder { <div>Loading…</div> }const Chart = defineAsyncComponent(() => import('./Chart.vue'));
<Chart /> <!-- emits fallback slot while loading -->import Chart from './Chart.svelte';
{#await import('./Chart.svelte') then { default: Chart }}<Chart />{/await}Legacy vs modern
Always-eager widget vs @defer (on viewport) widget
The eager version ships the heavy chart library in the initial bundle; the deferred version splits it into a chunk loaded only when the chart scrolls into view.
<section>
<app-heavy-chart [data]="series" /> <!-- ❌ OLD: eager — chart lib ships in initial bundle -->
</section><section>
@defer (on viewport) { <!-- ✅ NEW: @defer (on viewport) splits chart into a lazy chunk -->
<app-heavy-chart [data]="series" />
} @placeholder {
<div>Chart will appear here…</div>
}
</section>Interview gotchas
Context
Interviewers ask this to check whether you understand that deferrable views are a compiler feature, not just a template attribute.
The mechanical answer
The Angular compiler analyzes the @defer block, collects the standalone components and dependencies used inside it, and emits them into a separate render function wrapped in a dynamic import(). The block's template is compiled into a separate deferred view definition, and the runtime only instantiates it after the import resolves. Because it is compiler-driven, dependency hoisting is automatic — you get fine-grained chunking without manually orchestrating loaders, and unused imports stay out of the main chunk.
Trap
The common trap is claiming @defer only postpones rendering (runtime) and does nothing for bundle size. In fact the primary benefit is compile-time code-splitting; if you use @defer on a component that is also imported eagerly elsewhere, the component stays in the main bundle and the split never materializes. Interviewers love that catch.
Context
This question probes whether you think about deferred content as a client-only feature or understand the full SSR/hydration lifecycle.
The mechanical answer
During SSR/prerender, deferred blocks are rendered as their @placeholder (or skipped entirely when no placeholder is provided), because the trigger conditions — viewport, idle, hover — only make sense in a browser. The deferred component's HTML is not in the initial page source, so it does not contribute to first paint, but also cannot be displayed before JavaScript runs. After hydration, the triggers are armed and the block loads. This matters for SEO and perceived performance: critical content must never be deferred, and deferred content must always have a meaningful placeholder to prevent layout shift.
Trap
A naive candidate says 'SSR renders the deferred component too'. It does not — the trigger cannot fire on the server, and rendering it would defeat the purpose. The related trap is using on idle for above-the-fold content and then wondering why the hero image flashes in late; deferral is for non-critical subtrees only.