EthicsComputerCourses
Join training +91 90532 10052

Deferrable Views with @defer

Angular · Lesson 32 of 41 Advanced 7 min read Updated

By the end of this lesson you will be able to make an Angular page faster. You will load its heavy parts later, only when they are needed.

Not everything on a page is needed straight away. A sales chart at the bottom of a dashboard can wait. So can a reviews section behind a button, or a map in the footer. If you load them all at the start, the first download is bigger and the page is slower. That hurts most on a budget phone using 4G in a crowded hostel.

Think of a wedding buffet. The starters come out first, and the desserts arrive later when guests are ready for them. Nobody waits at the door for the dessert counter to be set up.

The @defer block gives Angular a similar instruction: "Split this part into a separate file. Load it later, when a trigger happens." A trigger is the event that starts loading, such as the user scrolling or clicking. The deferred part is called a deferrable view.

A first @defer block

Wrap the component you want to delay in @defer. Then give the user something to look at in the meantime with @placeholder:

TypeScript
import { Component } from '@angular/core';
import { SalesChart } from './sales-chart';

@Component({
  selector: 'app-dashboard',
  imports: [SalesChart],
  template: `
    <h2>Canteen dashboard</h2>

    @defer (on viewport) {
      <app-sales-chart />
    } @placeholder {
      <div class="chart-box">Chart will load here</div>
    }
  `,
})
export class Dashboard {}

Here is what happens, step by step:

  1. The page opens. You see only the heading and the placeholder box.
  2. You scroll down until the placeholder comes into view.
  3. Angular downloads the chart's code.
  4. The real chart replaces the placeholder.

SalesChart is used only inside @defer. So the build tool moves it into a separate chunk, a small JavaScript file that loads on its own. Anything that only SalesChart imports goes into that chunk too:

Output
Initial chunk files | Names           |  Raw size | Estimated transfer size
main-C2MRY2L4.js    | main            | 243.60 kB |                66.66 kB

Lazy chunk files    | Names           |  Raw size | Estimated transfer size
chunk-WXm2kRVR.js   | sales-chart     | 776 bytes |               776 bytes
chunk-DDJT6CZp.js   | reviews         | 361 bytes |               361 bytes

This only works when two rules are true:

  • The deferred components are standalone.
  • They are not used anywhere else in the same component outside @defer. For example, they must not appear in a viewChild query or in another part of the template.

If a component is used elsewhere, Angular must load it eagerly (straight away). The block still shows later, but you save no download.

The four blocks

BlockShown when
@deferThe main content, after it has loaded
@placeholderBefore loading starts (also used as the element for some triggers)
@loadingWhile the chunk is downloading
@errorIf loading fails, for example on a lost connection

On a fast connection, the placeholder and loading message can flash on and off too quickly. That flicker looks broken. @placeholder and @loading accept timing options to prevent it:

HTML
@defer (on viewport) {
  <app-sales-chart />
} @placeholder (minimum 500ms) {
  <div class="chart-box">Chart will load here</div>
} @loading (after 100ms; minimum 1s) {
  <p>Loading chart...</p>
} @error {
  <p>Could not load the chart. Check your connection.</p>
}
  • @placeholder (minimum 500ms): once the placeholder is shown, keep it for at least 500 ms.
  • @loading (after 100ms; minimum 1s): show the loading message only if loading takes longer than 100 ms. Once shown, keep it for at least 1 second, so it does not blink on and off.

Triggers

The words in brackets after @defer decide when loading starts:

TriggerStarts loading when
on idleThe browser is idle (the default if you give no trigger)
on viewportThe placeholder (or a given element) enters the visible area
on interactionThe user clicks or presses a key on the placeholder or a given element
on hoverThe mouse moves over the placeholder or a given element
on immediateRight after the rest of the page has rendered
on timer(3s)After a delay
when conditionA condition becomes true, for example when showMap()

The viewport, interaction and hover triggers watch the placeholder by default. So the placeholder must have exactly one root element (one outer tag).

You can also point these triggers at another element. Use a template reference variable, a name you give an element with #, such as #showReviews:

HTML
<button #showReviews>Show reviews</button>

@defer (on interaction(showReviews); prefetch on idle) {
  <app-reviews />
} @placeholder {
  <p>Click the button to read reviews.</p>
}

@defer (on timer(3s)) {
  <p>Offer: 10% off on combos after 3 pm!</p>
}

You can list several triggers, separated by semicolons. Loading starts on whichever comes first.

When you use when, prefer a signal, as in when showMap(). In a zoneless app (one without Zone.js watching every change), Angular may not notice a plain field changing. A signal tells Angular when it changes.

Prefetching

Prefetching separates downloading from showing. It is like a Swiggy order that is packed before you open the door. The food is ready, so the hand-over is instant.

In the reviews example above:

  1. prefetch on idle downloads the reviews chunk quietly while the browser is idle.
  2. The reviews stay hidden until the user clicks the button.
  3. When the user clicks, the code is already there, so the reviews appear instantly.

@defer and server-side rendering

During server-side rendering, the server builds the page's HTML before sending it. For deferred blocks, Angular renders the @placeholder (or nothing), not the deferred content itself.

Hydration is the step where the browser makes server-built HTML interactive. Incremental hydration does this one block at a time. It is on by default in Angular 22 when you use hydration. You can then add hydrate triggers such as @defer (hydrate on viewport):

  • The server renders the full content, so the first view is fast and search engines can read it.
  • The browser makes that block interactive only when the trigger fires.

When to use @defer

Good candidates:

  • Heavy components below the fold: charts, maps, rich text editors, video players.
  • Content behind a user action: reviews, comments, a "more details" panel.
  • Features most users never open, such as an admin toolbar.

Poor candidates:

  • Content visible immediately at the top of the page. Deferring it makes the layout jump and the page feel slower.
  • Tiny components. Each chunk is an extra request to the server.

Common mistakes

  • Using a deferred component elsewhere in the same template. It is then loaded eagerly, and @defer saves nothing.
  • A placeholder with several root elements for on viewport. Wrap the placeholder content in a single element, or point the trigger at another element.
  • Deferring above-the-fold content. It delays the most important part of the page.
  • Using plain fields with when. In a zoneless app the change may not be noticed. Use a signal.

Key points

  • @defer splits the components inside it into a lazy chunk and renders them when a trigger fires.
  • @placeholder, @loading and @error cover the time before, during and after a failed load; minimum and after prevent flicker.
  • Triggers include on idle (default), on viewport, on interaction, on hover, on immediate, on timer(...) and when.
  • prefetch downloads code early without showing it, so it appears instantly later.
  • Deferred components must be standalone and used only inside the @defer block to be split out.

Exercise

A college event page has two heavy components. CampusMap sits at the bottom of the page. PhotoGallery should open when the user clicks a "View photos" button. Write the template so that:

  1. The map loads when it scrolls into view, with a placeholder box until then.
  2. The gallery loads when the user clicks the button.
  3. The gallery's code is prefetched when the browser is idle.
  4. A "Loading photos..." message appears only if loading takes more than 200 ms.
Show solution
HTML
<h1>Annual Fest 2026, Jaipur Campus</h1>

<button #viewPhotos>View photos</button>
@defer (on interaction(viewPhotos); prefetch on idle) {
  <app-photo-gallery />
} @loading (after 200ms) {
  <p>Loading photos...</p>
} @placeholder {
  <p>Photos from last year's fest are ready to view.</p>
}

@defer (on viewport) {
  <app-campus-map />
} @placeholder {
  <div class="map-box">Campus map</div>
}

CampusMap and PhotoGallery must appear in the component's imports and nowhere else outside these blocks. Then each gets its own lazy chunk. The single <div> placeholder gives the viewport trigger one element to watch.

Quick Quiz

1. What does @defer do with the components inside it?

2. Which trigger is used if you write @defer { ... } with no trigger?

3. What does prefetch on idle add to @defer (on interaction; prefetch on idle)?

4. Why might @defer fail to create a lazy chunk for a component?

Learn Angular with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call