Jul 15, 2026

Angular @defer: Complete Guide to Deferrable Views

angulardeferfrontendtypescriptcontrol-flow

@defer lazy loads Angular components with viewport, interaction, and idle triggers. Cut your initial bundle without touching your TypeScript.

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 with prefetch on idle. No dynamic import() 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 condition
  • on idle — browser idle (the default)
  • on immediate — as soon as the template renders
  • on interaction(ref) — on click of a template reference
  • on timer(time) — after a delay
  • on hover(ref) — on mouse over a template reference
  • on 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.

Written by

Antonio Cárdenas

Google Developer Expert in Angular · Verified GDE profile · Google Developers profile

My technical writing reaches 100,000+ developers in English and Spanish. I've migrated production Angular apps from v8 to v22 — everything I publish is built from real project work, not documentation re-reads.

Frequently asked questions

FAQs

What is @defer in Angular and what is it for?

@defer is an Angular 17+ block that enables declarative lazy loading of components directly in the template. It automatically splits the bundle and loads the deferred content based on triggers like viewport, interaction, timer, or idle. It is the declarative alternative to dynamic import() calls in TypeScript.

What triggers are available in @defer?

The @defer triggers are: on idle (when the browser is idle, the default behavior), on immediate (as soon as the template renders), on viewport(ref) (when the element enters the viewport), on interaction(ref) (on click), on hover(ref) (on mouse over), on timer(1s) (after a delay), and when condition (a custom boolean expression).

What is the difference between @placeholder and @loading in @defer?

@placeholder shows static content while the deferred component has not started loading — it is what the user sees first. @loading shows content during the active download of the chunk. Configure @placeholder(minimum 500ms) to avoid flicker, and @loading(after 100ms; minimum 1s) to control when and for how long the loading indicator appears.

Does @defer affect SEO and Server-Side Rendering?

Content inside @defer is not server-rendered by default, which can affect SSR and SEO if used for critical content. For content that matters to search engines, use @placeholder with the visible content as a fallback, or configure Angular SSR to render the deferred block on the server. For secondary widgets like comments or maps, @defer is ideal.

Since which Angular version is @defer available?

@defer was introduced in Angular 17 (November 2023) as part of Deferrable Views. It is part of the Angular Renaissance together with the new Control Flow and Signals. It has been a stable public API since Angular 18 and is widely recommended for optimizing initial load performance.

Keep reading

Building something with Angular?

I write about Angular architecture, Signals and the tooling around them — migration guides, real upgrade paths, and the catches most posts skip.

Browse all articlesAbout me

More from the blog