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 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:
- They must be standalone. Non-standalone dependencies cannot be deferred and are still eagerly loaded, even if they are inside of
@defer
blocks. - They cannot be referenced outside of
@defer
blocks within the same file. If they are referenced outside 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.
Angular's compiler produces a dynamic import statement for each component, directive, and pipe used in the @defer
block. The main content of the block renders after all the imports resolve. Angular does not guarantee any particular order for these imports.
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 shownafter
- 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.
<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 mouseover
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.