Using Angular @defer
With the release of Angular 17 (stable in Angular 18), came an exciting new feature to the framework, which is known as deferrable views, but more commonly called @defer blocks. This powerful new tool can be used to optimize app performance by deferring the loading of components and other resources, in turn, reducing the initial bundle size of an application. In this article I will walk you through the concept, syntax, usage, and best practices for working with @defer.
Intro to @defer
Have you ever written or worked on an application and looked for ways to improve performance, or wondered why it was necessary to load even non-critical pieces of your application when there’s a chance they may not be needed or immediately used? Well this is where @defer is a game changer.
Up until now, Angular only had one way of avoiding loading unnecessary resources and that was through router based, lazy loading. While this feature is great, it did not allow lazy loading of particular parts of a template/component. Angular’s @defer syntax provides a clean, declarative way to lazily load parts of your UI based on user interaction, visibility in the viewport, or other conditions.
Basic Syntax
Below is a very simple example of the @defer syntax. This is done within the component’s template.
@defer {
<large-lazy-component/>
}
@defer delays code download, execution, and component instantiation until a defined condition is met. There are multiple ways we can control the behavior of the @defer block, displaying content in its place, display content during loading, and error handling. We will cover all of this. Let’s dive in!
When to use
Use @defer when:
- A component is not immediately needed on initial render.
- You want to improve performance by reducing initial bundle size.
- Loading depends on user behavior or visibility in the viewport.
Which dependencies can be “deferred”?
- Components, directives, pipes, and component CSS styles can be deferred
- Dependencies in
@deferblock must meet 2 conditions to be deferred- Be standalone – Only standalone components, directives, and pipes can be deferred.
- Be isolated – The deferred dependency must not be referenced outside of the
@deferblock in the same file, this includes using@ViewChildqueries
One thing to note is that transitive dependencies, such as directives and pipes, do not need to be strictly standalone. They can be declared in an @NgModule and not cause the dependencies to be eagerly loaded.
@defer vs @if
You may be wondering how @defer is any different than a standard @if block. Let’s explain.
The @if block shown in the example below, will only control the visibility of the component. The component is still shipped immediately even if it is not rendered, but it is not created until the condition is met. This does nothing for optimizing performance.
@if (showComponent) {
<large-lazy-component></large-lazy-component>
}
Defer on the other hand, will control both loading and rendering. Both the components and its dependencies are lazy-loaded.
So, if you simply want to control the visibility of a component, use @if, but if you want to control the visibility and optimize performance, use the @defer block. Another thing to note is that with @if you can constantly switch between states, but once a @defer condition is met, that’s it. There can be no more ‘toggle’ like behavior. The table below shows a comparison of what each can do to help better understand the differences between the two.
| Feature | @if | @defer |
|---|---|---|
| Controls visibility | ✅ | ✅ |
| Controls loading | ❌ | ✅ |
| Reduces bundle size | ❌ | ✅ |
| Allows placeholders / loaders | ❌ | ✅ |
Basic Usage
Within a @defer block, you can define and handle different stages of the loading process using @placeholder, @loading, and @error. At first a @defer block doesn’t contain any content until a certain process is triggered. In this case, you can use the optional @loading or @placeholder blocks. The @placeholder is used to display content before the @defer block is triggered. Let’s take a look at the example below.
@placeholder example
@defer {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
}
Any content can be used within the @placeholder block, such as HTML, components, pipes, etc. It is also good to note that @placeholder can also accept an optional parameter used to specify the minimum time the placeholder block should be rendered after it is initially rendered. This parameter is in time increment of seconds (s) or milliseconds (ms).
@defer {
<my-large-component></my-large-component>
} @placeholder(minimum 1000ms) {
<p>This is my placeholder content</p>
}
Adding in @loading
Like @placeholder, @loading is also an optional block. Take note that the @loading block takes place of the @placeholder block once loading is triggered. This is done by triggers in the @defer block, which we’ll get into shortly.
The @loading block defines what should be rendered while the content in the @defer block is being loaded. Take a look at the example below.
@defer {
<my-large-component></my-large-component>
} @placeholder(minimum 1000ms) {
<p>This is my placeholder content</p>
} @loading {
<p>Deferred content is loading... </p>
}
The @loading block can also accept two optional parameters
- minimum
- after
The minimum parameter is the minimum amount of time that the content in the loading block is displayed and after is used to set the amount of time to wait after loading begins, to show the loading content. In the example below, the content will be displayed a half a second after loading begins and will display for a minimum of 1 second before it is rendered.
@defer {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
} @loading (after 500ms, minimum 1s) {
<p>Deferred content is loading... </p>
}
@error handling in defer blocks
Error blocks are also optional blocks used to display content when the deferred loading fails. This can sometimes happen due to a bad network condition, bugs in your code such as trying to use non-standalone component in your defer block. Like the dependencies of @placeholder and @loading blocks, dependencies of @error blocks are also eagerly loaded. Below is an example of the @error block.
@defer {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
} @loading {
<p>Deferred content is loading... </p>
} @error {
<p>Deferred content failed to load</p>
}
Now that we’ve covered the blocks used in deferred loading, let’s take a look at how we can control @defer blocks using various conditions or “triggers”.
Controlling @defer blocks
@defer block loading can be controlled using what are known as triggers. Angular has built-in triggers and you can also define custom triggers. To specify when a built-in trigger is used, you will use the keyword on. When using custom defined triggers, you will need to use the keyword when.
First, we’ll take a look at the built-in triggers that Angular provides for us. Below is a table listing those pre-defined triggers and what they do. We’ll then dive into each one separately to see how they can be used with our deferred content.
| Trigger | Description |
|---|---|
| idle | When the browser is idle (default behavior of a @defer block) |
| viewport | When specific content enters into the viewport |
| interaction | When a user interacts with a specific element of the page (clicks or keydown events) |
| hover | When a user hovers the mouse over a specified area of the page |
| immediate | After the parent component is rendered |
| timer | When a defined amount of time has passed |
The idle trigger
The idle trigger is the default of a @defer block, so if no trigger is defined idle will be used, but you can also set this in your block. Angular determines when a page is idle by using the requestIdleCallback API. A page is considered idle when all resources are loaded and all tasks are complete. Below is an example of the idle trigger being used. Remember, this can be left out since it is default behavior.
@defer (on idle) {
<my-large-component></my-large-component>
}
The viewport trigger
The viewport trigger is used to load deferred content when specific content enters the viewport. This can be done two ways. The first is when @placeholder content enters the viewport, the other is when a template referenced variable enter the viewport. Let’s take a look at both ways of using viewport. First we’ll take a look at just using the @placeholder.
@defer (on viewport) {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
}
In this example, as soon as the @placeholder content is visible in the viewport, the deferred content will be rendered and available to the user.
Now, let’s take a look at an example using a template referenced variable.
<div #productHeader>Product Header content</div>
@defer (on viewport(productHeader)) {
<my-large-component></my-large-component>
}
With this approach, the deferred content will now be loaded as soon as the #productHeader div becomes visible in the viewport. This can be used in place of the @placeholder block.
The interaction trigger
The interaction trigger will load deferred content when either our @placeholder or template referenced variable is interacted with via a click or keydown event. See the example below.
@defer (on interaction) {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
}
Just like the viewport trigger, interaction can also use a template referenced variable to decide which element that is interacted with will cause the deferred content to load. Let’s take a look.
<div #myClickableDiv>Click me</div>
@defer (on interaction(myClickableDiv)) {
<my-large-component></my-large-component>
}
Now, instead of using a placeholder for interaction, the deferred content will load when there is a click or keydown event that takes place on the #myClickableDiv div.
The hover trigger
Like the other built-in triggers, hover is pretty self explanatory. When a user hovers over the placeholder content with a mouseover or focusin event, which fires when an element has received focus, after the focus event. Below is an example of this using @placeholder.
@defer (on hover) {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
}
The hover trigger can also use a template referenced variable like interaction and viewport. As you can see from the example below, the set up is identical other than the trigger name.
<div #hoverDiv>Hover the mouse on me</div>
@defer (on hover(hoverDiv)) {
<my-large-component></my-large-component>
}
The immediate trigger
The immediate trigger does as the name suggests. It loads the deferred content immediately after all non-deferred has finished loading. This can seem very similar to the idle trigger, but unlike the idle trigger, immediate starts loading as soon as Angular finishes initial rendering and does not wait for browser idle time. Therefore, it has a higher priority than idle and fire almost immediately after change detection stabilizes.
@defer (on immediate) {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
}
Unlike viewport, interaction, and hover, the immediate trigger can not be used with a template referenced variable.
The timer trigger
If you want your deferred content to load after a specified amount of time, you can use the timer trigger. The time will be specified using either milliseconds (ms) or seconds (s). Once the specified time has passed, the deferred content will load. Check out the example below.
@defer (on timer(1500ms)) {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
}
In the above example, our deferred content will be loaded after 1500 milliseconds.
When: Custom @defer conditional expressions
When the built-in triggers for @defer aren’t enough to meet your needs, there is also the option of using the when trigger. The when trigger of a @defer block is used when a custom condition needs to be met for the deferred content to be rendered. This could be something like when a user is authenticated or when a feature flag is set for your application. Let’s take a look at the when trigger and how to set it up.
@defer (when featureEnabled('feature')) {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
}
Please note that this operation only happens once. If the condition changes to a falsy value, the deferred content does not revert back to the placeholder.
Prefetching data with @defer blocks
Unlike built-in triggers using on and custom triggers using when, which let Angular know when to load the deferred content, the prefetch trigger is used to load data or resources of the component before it is rendered. Let’s take a look at an example to see how to use prefetch and explain what is going on.
@defer (on viewport; prefetch on idle) {
<my-large-component></my-large-component>
} @placeholder {
<p>This is my placeholder content</p>
}
In this example, our deferred component does not become rendered until it is visible in the viewport, but the data associated with the component starts loading when the browser is idle. This could be used for something like loading product data to display in a product component before a user interacts with a component or a component becomes visible, etc. It makes for a smoother user experience since the data is prefetched and possibly finished loading before the component is rendered.
Best practices
When working with deferrable views, there are a few things we want to keep in mind for providing the best user experience.
- Avoid cascading loads
- When using nested
@deferblocks, give them different triggers to prevent simultaneous loading and excessive network requests.
- When using nested
- Avoid layout shifts
- Don’t defer components that are visible in the viewport on initial load, as this can hurt Core Web Vitals (CLS).
- If necessary, avoid triggers like
immediate,timer,viewport, or customwhenduring initial render.
- Keep accessibility in mind
- Screen readers may not announce content changes when deferred content loads.
- Wrap
@deferblocks in an element witharia-live="polite"andaria-atomic="true"to ensure updates are announced.
Key points to remember
@deferlets Angular lazily load parts of a template, reducing initial bundle size and improving performance.- It replaces the need to load non-critical components on initial render.
- Works with standalone + isolated components, directives, and pipes.
- Supports optional blocks:
@placeholder– shown before loading starts@loading– shown while loading@error– shown if loading fails
- Unlike
@if, which only hides content,@deferactually delays loading and rendering. - Loading can be triggered by: idle (default), viewport, interaction, hover, immediate, timer.
whenallows custom conditions, andprefetchcan load data early without rendering.- Triggers run once, and placeholder/loading/error content is not deferred.
If you would like to dive a bit deeper into using deferrable views I have provided a stackblitz demo along with some other helpful links to help you become more familiar with them and hopefully discover ways that you can use them in your own applications.
- My Stackblitz demo - Angular Starter - StackBlitz
- Angular official docs - https://angular.dev/guide/templates/defer
- Angular deferrable views tutorial - https://angular.dev/tutorials/deferrable-views/1-what-are-deferrable-views
Happy coding!