In-depth Guides
Templates

Deferred loading with @defer

Deferrable views, also known as @defer blocks, reduce the initial bundle size of your application by deferring the loading of code that is not strictly necessary for the initial rendering of a page. This often results in a faster initial load and improvement in Core Web Vitals (CWV), primarily Largest Contentful Paint (LCP) and Time to First Byte (TTFB).

To use this feature, you can declaratively wrap a section of your template in a @defer block:

      
@defer {  <large-component />}

The code for any components, directives, and pipes inside of the @defer block is split into a separate JavaScript file and loaded only when necessary, after the rest of the template has been rendered.

Deferrable views support a variety of triggers, prefetching options, and sub-blocks for placeholder, loading, and error state management.

Which dependencies are deferred?

Components, directives, pipes, and any component CSS styles can be deferred when loading an application.

In order for the dependencies within a @defer block to be deferred, they need to meet two conditions:

  1. They must be standalone. Non-stadalone dependencies cannot be deferred and are still eagerly loaded, even if they are inside of @defer blocks.
  2. They cannot be referenced outside of @defer blocks within the same file. If they are referenced outside of the @defer block or referenced within ViewChild queries, the dependencies will be eagerly loaded.

The transitive dependencies of the components, directives and pipes used in the @defer block do not strictly need to be standalone; transitive dependencies can still be declared in an NgModule and participate in deferred loading.

How to manage different stages of deferred loading

@defer blocks have several sub blocks to allow you to gracefully handle different stages in the deferred loading process.

@defer

This is the primary block that defines the section of content that is lazily loaded. It is not rendered initially– deferred content loads and renders once the specified trigger occurs or the when condition is met.

By default, a @defer block is triggered when the browser state becomes idle.

      
@defer {  <large-component />}

Show placeholder content with @placeholder

By default, defer blocks do not render any content before they are triggered.

The @placeholder is an optional block that declares what content to show before the @defer block is triggered.

      
@defer {  <large-component />} @placeholder {  <p>Placeholder content</p>}

While optional, certain triggers may require the presence of either a @placeholder or a template reference variable to function. See the Triggers section for more details.

Angular replaces placeholder content with the main content once loading is complete. You can use any content in the placeholder section including plain HTML, components, directives, and pipes. Keep in mind the dependencies of the placeholder block are eagerly loaded.

The @placeholder block accepts an optional parameter to specify the minimum amount of time that this placeholder should be shown after the placeholder content initially renders.

      
@defer {  <large-component />} @placeholder (minimum 500ms) {  <p>Placeholder content</p>}

This minimum parameter is specified in time increments of milliseconds (ms) or seconds (s). You can use this parameter to prevent fast flickering of placeholder content in the case that the deferred dependencies are fetched quickly.

Show loading content with @loading

The @loading block is an optional block that allows you to declare content that is shown while deferred dependencies are loading. It replaces the @placeholder block once loading is triggered.

      
@defer {  <large-component />} @loading {  <img alt="loading..." src="loading.gif" />} @placeholder {  <p>Placeholder content</p>}

Its dependencies are eagerly loaded (similar to @placeholder).

The @loading block accepts two optional parameters to help prevent fast flickering of content that may occur when deferred dependencies are fetched quickly,:

  • minimum - the minimum amount of time that this placeholder should be shown
  • after - the amount of time to wait after loading begins before showing the loading template
      
@defer {  <large-component />} @loading (after 100ms; minimum 1s) {  <img alt="loading..." src="loading.gif" />}

Both parameters are specified in time increments of milliseconds (ms) or seconds (s). In addition, the timers for both parameters begin immediately after the loading has been triggered.

Show error state when deferred loading fails with @error

The @error block is an optional block that displays if deferred loading fails. Similar to @placeholder and @loading, the dependencies of the @error block are eagerly loaded.

      
@defer {  <large-component />} @error {  <p>Failed to load large component.</p>}

Controlling deferred content loading with triggers

You can specify triggers that control when Angular loads and displays deferred content.

When a @defer block is triggered, it replaces placeholder content with lazily loaded content.

Multiple event triggers can be defined by separating them with a semicolon, ; and will be evaluated as OR conditions.

There are two types of triggers: on and when.

on

on specifies a condition for when the @defer block is triggered.

The available triggers are as follows:

Trigger Description
idle Triggers when the browser is idle.
viewport Triggers when specified content enters the viewport
interaction Triggers when the user interacts with specified element
hover Triggers when the mouse hovers over specified area
immediate Triggers immediately after non-deferred content has finished rendering
timer Triggers after a specific duration

idle

The idle trigger loads the deferred content once the browser has reached an idle state, based on requestIdleCallback. This is the default behavior with a defer block.

      
<!-- @defer (on idle) -->@defer {  <large-cmp />} @placeholder {  <div>Large component placeholder</div>}

viewport

The viewport trigger loads the deferred content when the specified content enters the viewport using the Intersection Observer API. Observed content may be @placeholder content or an explicit element reference.

By default, the @defer watches for the placeholder entering the viewport. Placeholders used this way must have a single root element.

      
@defer (on viewport) {  <large-cmp />} @placeholder {  <div>Large component placeholder</div>}

Alternatively, you can specify a template reference variable in the same template as the @defer block as the element that is watched to enter the viewport. This variable is passed in as a parameter on the viewport trigger.

      
<div #greeting>Hello!</div>@defer (on viewport(greeting)) {  <greetings-cmp />}

interaction

The interaction trigger loads the deferred content when the user interacts with the specified element through click or keydown events.

By default, the placeholder acts as the interaction element. Placeholders used this way must have a single root element.

      
@defer (on interaction) {  <large-cmp />} @placeholder {  <div>Large component placeholder</div>}

Alternatively, you can specify a template reference variable in the same template as the @defer block as the element that is watched to enter the viewport. This variable is passed in as a parameter on the viewport trigger.Z

      
<div #greeting>Hello!</div>@defer (on interaction(greeting)) {  <greetings-cmp />}

hover

The hover trigger loads the deferred content when the mouse has hovered over the triggered area through the mouseenter and focusin events.

By default, the placeholder acts as the interaction element. Placeholders used this way must have a single root element.

      
@defer (on hover) {  <large-cmp />} @placeholder {  <div>Large component placeholder</div>}

Alternatively, you can specify a template reference variable in the same template as the @defer block as the element that is watched to enter the viewport. This variable is passed in as a parameter on the viewport trigger.

      
<div #greeting>Hello!</div>@defer (on hover(greeting)) {  <greetings-cmp />}

immediate

The immediate trigger loads the deferred content immediately. This means that the deferred block loads as soon as all other non-deferred content has finished rendering.

      
@defer (on immediate) {  <large-cmp />} @placeholder {  <div>Large component placeholder</div>}

timer

The timer trigger loads the deferred content after a specified duration.

      
@defer (on timer(500ms)) {  <large-cmp />} @placeholder {  <div>Large component placeholder</div>}

The duration parameter must be specified in milliseconds (ms) or seconds (s).

when

The when trigger accepts a custom conditional expression and loads the deferred content when the condition becomes truthy.

      
@defer (when condition) {  <large-cmp />} @placeholder {  <div>Large component placeholder</div>}

This is a one-time operation– the @defer block does not revert back to the placeholder if the condition changes to a falsy value after becoming truthy.

Prefetching data with prefetch

In addition to specifying a condition that determines when deferred content is shown, you can optionally specify a prefetch trigger. This trigger lets you load the JavaScript associated with the @defer block before the deferred content is shown.

Prefetching enables more advanced behaviors, such as letting you start to prefetch resources before a user has actually seen or interacted with a defer block, but might interact with it soon, making the resources available faster.

You can specify a prefetch trigger similarly to the block's main trigger, but prefixed with the prefetch keyword. The block's main trigger and prefetch trigger are separated with a semi-colon character (;).

In the example below, the prefetching starts when a browser becomes idle and the contents of the block is rendered only once the user interacts with the placeholder.

      
@defer (on interaction; prefetch on idle) {  <large-cmp />} @placeholder {  <div>Large component placeholder</div>}

Testing @defer blocks

Angular provides TestBed APIs to simplify the process of testing @defer blocks and triggering different states during testing. By default, @defer blocks in tests play through like a defer block would behave in a real application. If you want to manually step through states, you can switch the defer block behavior to Manual in the TestBed configuration.

      
it('should render a defer block in different states', async () => {  // configures the defer block behavior to start in "paused" state for manual control.  TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual});  @Component({    // ...    template: `      @defer {        <large-component />      } @placeholder {        Placeholder      } @loading {        Loading...      }    `  })  class ComponentA {}  // Create component fixture.  const componentFixture = TestBed.createComponent(ComponentA);  // Retrieve the list of all defer block fixtures and get the first block.  const deferBlockFixture = (await componentFixture.getDeferBlocks())[0];  // Renders placeholder state by default.  expect(componentFixture.nativeElement.innerHTML).toContain('Placeholder');  // Render loading state and verify rendered output.  await deferBlockFixture.render(DeferBlockState.Loading);  expect(componentFixture.nativeElement.innerHTML).toContain('Loading');  // Render final state and verify the output.  await deferBlockFixture.render(DeferBlockState.Complete);  expect(componentFixture.nativeElement.innerHTML).toContain('large works!');});

Does @defer work with NgModule?

@defer blocks are compatible with both standalone and NgModule-based components, directives and pipes. However, only standalone components, directives and pipes can be deferred. NgModule-based dependencies are not deferred and are included in the eagerly loaded bundle.

How does @defer work with server-side rendering (SSR) and static-site generation (SSG)?

When rendering an application on the server (either using SSR or SSG), defer blocks always render their @placeholder (or nothing if a placeholder is not specified).

Triggers are ignored on the server.

Best practices for deferring views

Avoid cascading loads with nested @defer blocks

When you have nested @defer blocks, they should have different triggers in order to avoid loading simultaneously, which causes cascading requests and may negatively impact page load performance.

Avoid layout shifts

Avoid deferring components that are visible in the user’s viewport on initial load. Doing this may negatively affect Core Web Vitals by causing an increase in cumulative layout shift (CLS).

In the event this is necessary, avoid immediate, timer, viewport, and custom when triggers that cause the content to load during the initial page render.