Angular 17 Defer - Improve Performance in Your Application
The feature that impressed me mostly in Angular 17 is called defer or deferrable views. This is why in this post you will learn everything that you need to know about how to create them and why they are so awesome.
Deferrable views
First of all you must understand what is a deferrable view.
This is a view which will be rendered not initially but after some time or based on some condition.
Why do we need that at all? Just imagine that we have a huge Angular application and we are rendering lots of stuff on the screen, different components and probably most of the components are not needed on initialize. Which actually means that we can defer them and not show or load them at the beginning. It will make our application much faster.
So the main idea is to load for user only information which is needed on initialize and then all additional information we can load later. And in Angular 17 it is possible to do that in extremely comfortable and flexible way with deferred views.
Basic defer
Here I prepared an app-child
component which we want to render with defer.
@Component({
selector: 'app-child',
standalone: true,
imports: [CommonModule, RouterOutlet],
template: 'Child',
})
export class ChildComponent {}
The main pain is that we don't need our child component on initialize. This is a component which we can load later.
Also here I have some plain markup.
<h1>Monsterlessons Academy</h1>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
Now let's try to use the simplest defer
<div>Content</div>
@defer {
<app-child></app-child>
}
The only difference that you can notice is that Child
text blinks after page reload but all content don't. It happens because defer by default has a trigger on idle
.
on idle means that Angular will render this piece of code when more important tasks are finished.
But most importantly what happens in network. As you can see on screen we loaded our ChildComponent
as an additional chunk of Javascript. It was not bundled with our main bundle. It is an additional chunk and just by writing this @defer
Angular loads this piece of code dynamically.
@defer (on idle) {
<app-child></app-child>
}
Previous block that we wrote is the same like this block with on idle
trigger inside. It is a default behaviour of defer.
Triggers
Now let's look on all other trigger that we get inside deferrable views. The most popular trigger is for sure on viewport
. It means that we are loading our block only when it becomes visible in the viewport. If it is outside of the viewport we don't load it at all.
@defer (on viewport) {
<app-child></app-child>
}
But as you can see we are getting an error that we are missing a @placeholder
block because we must have something to render when our view is not rendered yet.
@defer (on viewport) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
As you can see when our element is off screen we render a placeholder instead. But as soon as we see this element on screen it will render our ChildComponent
.
Most importantly we didn't load that chunk at all until we need it and see in viewport.
Another possibility is to use on interaction
@defer (on interaction) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
It will trigger loading for example after we click on the element. We don't need to bind any additional click events here. It just works out of the box. For example it makes a lot of sense to implement Load more
button with such functionality.
Another possibility is to use on hover
.
@defer (on hover) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
In the same way like we had click event here we have mouse hover event.
Another trigger that we have is on immediate
.
@defer (on interaction) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
It means that we try to load this block as soon as possible after full page rendering.
The last trigger that we have is on timer
.
@defer (on timer(2000)) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
It will load our element after the defined timeout.
Combining triggers
It is also important to remember that we can combine multiple triggers.
@defer (on hover; on timer(2000)) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
We separate several triggers with semicolon. In this case our defer will be triggered either on hover or after 2 seconds of delay. We can write any amount of triggers to combine them.
When condition
Another thing that we have inside defer is when
. We can write a condition inside defer
to trigger it.
Let's create a button which changes isVisible
property.
export class AppComponent {
isVisible = false;
}
Now we need a button and a condition.
<button (click)="isVisible=true">Show</button>
@defer (when isVisible) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
This defer will be triggered when we set isVisible
property to true
.
Our component will never be loaded until we click on the button to set our condition to true
.
Which means we can mix component logic with deferrable views.
Defer vs ngIf
And the most popular question that I see on the internet regarding defers is "Does defer replaces ng-if? What should we use?". You must understand that these are two completely different things. ngIf
simply removes something from DOM. Defer works completely different. It doesn't load anything at all and it doesn't execute your component but it stores it inside additional chunk and loads it later when needed.
It is a completely different behavior and you should not replace all your ngIf
blocks with defer. But if you render in loop 1000 elements it is more performant to use defer because it won't do anything at all until we tell Angular that we need to load and execute this block.
Prefetch your chunk
Now we must talk about prefetch
inside defer
. We can prefetch our chunk before we use it. Until now we didn't control it which means we load it only when defer must be used.
export class AppComponent {
isVisible = false;
prefetchCondition = false;
}
I created here additional prefetchCondition
property which we need to set to true
to trigger loading of the chunk.
<button (click)="isVisible=true">Show</button>
<button (click)="prefetchCondition=true">Load</button>
@defer (when isVisible; prefetch when prefetchCondition) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
Here here we have a button and a prefetch
condition. It means that when we click prefetch
button it will load the chunk and when we click on isVisible
it will execute it.
As you can see after clicking on load we loaded a chunk but it didn't show the content.
Another variant to preload a chunk is when we have resources for that.
@defer (when isVisible; prefetch on idle) {
<app-child></app-child>
}
@placeholder {
<div>Placeholder</div>
}
In this case it will load it as soon as possible to be used later.
Full notation of defer
The last thing that I want to show you is a full notation of defer block.
@defer (on viewport) {
<app-child></app-child>
}
@loading (after 150ms; minimum 150ms) {
<div>Loading</div>
}
@placeholder (minimum 150ms) {
<div>Placeholder</div>
}
@error {
<div>Error</div>
}
First we have our @defer
with content that we want to load in chunk inside. If it takes more less than 150ms to load it we will render a placeholder but for at least 150ms so it doesn't confuse a user. If it takes longer that 150ms it renders a loading block instead. And if we get some error that @error
block will be shows. So here we can specify how long we want to show each block.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done