Angular 17 Defer - Improve Performance in Your Application
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>
}

Basic defer

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.

Separate chunk

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>
}

viewport error

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>
}

Viewport placeholder

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.

condition

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.

Load chunk

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.

And actually if you want to improve your Angular knowledge and prepare for the interview I highly recommend you to check my course Angular Interview Questions - Coding Interview 2023.

📚 Source code of what we've done