TL;DR: Wrap heavy components in
@defer { }and Angular automatically code-splits them out of your initial bundle. Control when they load with triggers (on viewport,on interaction,on idle,on timer), show fallbacks with@placeholder,@loading, and@error, and prefetch withprefetch on idle. No dynamicimport()needed.
You can think of @defer as a setTimeout() for template content — except it gives you far more options and functionality:
setTimeout(() => {
console.log("Delayed by 1 second.");
}, 1000);setTimeout only gives you a delay. @defer gives you the delay plus a placeholder to show while you wait, plus automatic bundle splitting:
@defer (on viewport(myPlaceholder); on timer(1s)) {
<app-child />
} @placeholder (minimum 1000ms) {
<div #myPlaceholder>Loading soon…</div>
}@defer supports several companion blocks, each explicit about its job:
@placeholder— static content shown before loading starts@loading— content shown while the chunk downloads@error— content shown if loading fails
And you can combine multiple triggers:
when isVisible— custom boolean conditionon idle— browser idle (the default)on immediate— as soon as the template renderson interaction(ref)— on click of a template referenceon timer(time)— after a delayon hover(ref)— on mouse over a template referenceon viewport(ref)— when a reference enters the viewport
when
A condition tied to a variable, a function call, a piped value — any boolean expression. Note that Observables and async pipes can’t be used directly here; convert them to a signal first.
on idle
The default behavior: the deferred content loads when the browser is idle.
on immediate
Fires as soon as the template renders. Useful when you want the bundle split but the content loaded right away.
on timer(1s)
A setTimeout-based trigger. Use s for seconds or ms for milliseconds after the number.
on interaction(ref)
Fires when the user clicks the referenced element — the #trigger template reference:
<button #trigger class="btn btn-red">Action button</button>on hover(ref)
Fires when the user hovers the referenced element:
<button #triggerHover class="btn btn-red">Hover button</button>on viewport(ref)
Fires when the referenced element scrolls into the viewport — the classic pattern for below-the-fold content.
Combining everything
Separate triggers with semicolons and add prefetch conditions:
@defer (when isVisible() && isAuth; prefetch on immediate; prefetch when isDataLoaded()) {
<big-component />
} @placeholder (minimum 500ms) {
Placeholder content!
} @loading (minimum 1s; after 100ms) {
Loading…
} @error {
Loading failed :(
}This defers until the content is visible and the user is authenticated, prefetches the chunk as soon as the template is ready, and layers the three fallback blocks: @placeholder for static content, @loading for a skeleton or spinner so the user knows something is happening, and @error for a message or retry action.
All of this composes with the new Control Flow — nest it inside @if, @else, and @for however you need.
@defer and @for: order matters
@defer inside a @for:
@for (book of books; track book.id) {
@defer {
<big-component />
}
}@for inside a @defer:
@defer {
@for (book of books; track book.id) {
<big-component />
}
}In the first example, every book in the @for creates an embedded view plus a nested embedded view for each @defer. With 100 books that’s 100 × 2 + 100 embedded views — one deferred loading decision per item.
In the second example there is only one @defer interaction: a single deferred view containing books.length + 1 views for the loop.
Which one you want depends on the use case: per-item deferral makes sense when each item hosts a genuinely heavy component; wrapping the whole list is cheaper when the list itself is the heavy part.
Bonus: what the compiler does
How does this work under the hood? Simple:
@defer {
<big-component />
}becomes, roughly:
function defer_block_deps() {
return [() => import('./big-component')];
}The compiler turns each @defer block into a dynamic import() — the same code splitting you used to wire up by hand, now declarative in the template.
@defer is one of the pillars of the Angular Renaissance, alongside Signals and the new Control Flow.