Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to…

Follow publication

Angular — Deferred Loading Using Defer Block — What You Need to Know

Angular — Deferred Loading using @defer block by Madhu Sudhanan

Lazy loading in Angular is an important feature that every developer should know. It helps load components/parts/code on demand and improves the UX by loading what is needed at the moment.

Angular Lazy loading was purely routing-based and to load non-route modules you need to use special code which involves import and ngComponentOutlet. Last year, I faced a similar challenge and achieved this in a possible declarative way. You can see this in the below post.

https://maddydeep28.medium.com/lazy-loading-non-routable-angular-modules-imperative-declarative-pattern-6a0cea0356e9

The previous solution I used has some pitfalls and it’s not a 100% declarative way. But with the new @deferblock syntax and standalone component, you can do the deferred loading in a much simpler and more declarative way.

Let’s see what’s new with the defer block.

Basic defer block

A basic defer block initialization will look like below. It does contain any condition so basically the content will be loaded immediately. In the following section, let’s see some complex examples using the defer block.

@defer {
<app-defer></app-defer>
}

So how does the defer block lazy load its content? Basically during the compilation, the component(s) and its dependencies(directives, pipes, etc.) will be packed into a separate chunk and will loaded when the defer block conditions are met. You can see that in the below image.

CMD — Lazy chunk files
Defer load chunk files

Placeholder block

As the name implies, the @placeholder block will act as the placeholder for the content that will be defer loaded. This is an optional block.

<input type="checkbox" #check/>
@defer (on interaction(check)) {
<app-defer></app-defer>
} @placeholder {
<span>Loading</span>
}

You can mention the minimum time to show the placeholder block before swapping to the next component by using the minimum parameter.

In addition to the placeholder purpose, you might need to use a placeholder if you want to use a zero parameter interaction, hover, viewport triggers. Now the placeholder block will act as a target for these triggers.

@defer (on interaction) {
<app-defer></app-defer>
} @placeholder {
<span>Loading</span>
}

The content of the @placeholder, @loading and @error are eagerly loaded.

Loading block

The next optional block that allows you to have a smooth transition to the next component is the @loading block.

@defer (on interaction(check)) {
<app-defer></app-defer>
} @placeholder {
<span>Placeholder</span>
} @loading {
<span>Loading</span>
}

The content of the @placeholder, @loading and @error are eagerly loaded.

Error block

The @error block will render its content when any error happens during the loading of the deferred content.

<input type="checkbox" #check/>

@defer (on interaction(check)) {
<app-defer></app-defer>
} @placeholder {
<span>Placeholder</span>
} @loading {
<span>Loading</span>
} @error {
<span>Error Loading...</span>
}

The content of the @placeholder, @loading and @error are eagerly loaded.

Deferred loading conditions using on, when, and prefetch

I’d say that the Angular team did amazing work here by providing both declarative and imperative approaches to lazy load the content.

The declarative conditions can be provided using the on whereas the where allows the developer to provide their own logic.

<input type="checkbox" #check/>

@defer (on interaction(check)) {
<app-defer></app-defer>
}
@defer (when showSignal()) { // showSignal is a signal.
<app-defer></app-defer>
}

Using on (Declarative trigger conditions)

You can use the following declarative trigger conditions to defer the load of your component.

1. immediate
2. idle
3. interaction/hover
4. viewport
5. timer

You can use multiple declarative triggers for a single defer block. All the trigger conditions are combined using OR logic. For instance, the below code will load content either the button is clicked or the checkbox is hovered.

<button #button>Load Defer Component</button>

<input type="checkbox" #check/>

@defer (on interaction(button), hover(check)) {
<app-defer></app-defer>
} @placeholder {
<span>Loading</span>
}

Immediate

The immediate condition will load the defer block content immediately once the browser is ready.

@defer (on immediate) {
<app-defer></app-defer>
}

idle

This is the default behavior of the defer block and would render the content when the browser is in an idle state.

@defer (on idle) {
<app-defer></app-defer>
}

interaction

The deferred loading will start when the given element is interacted with. The interaction condition would accept a template reference variable as a parameter and will load its content when the element is interacted.

<input type="checkbox" #check/>

@defer (on interaction(check)) {
<app-defer></app-defer>
}

So now you may have a question about the interaction. What are the events that are used for the interaction? At the time of writing this post, the interaction is decided by two events — click and keydown. Probably there will be more events to be supported in the future.

hover

The content will be loaded on the hover of the target element.

<input type="checkbox" #check/>

@defer (on hover(check)) {
<app-defer></app-defer>
}

viewport

This is another fantastic declarative check to have. Now the defer block will load its content when the element specified in the viewport or the content itself is in the visible area.

<div #container>
.....
</div>

@defer (on viewport(container)) {
<app-defer></app-defer>
}

You can also mention the viewport condition to load its content when the actual content to be rendered is in the viewport area. But it will require you to mention the @placeholder block by which the framework will find the viewport enter event.

@defer (on viewport) {
<app-defer></app-defer>
} @placeholder {
<span>Loading</span>
}

timer

You can mention the timer trigger condition to load the content after a specific time. The below code will render the content after 2s.

@defer (on timer(2000)) {
<app-defer></app-defer>
} @placeholder {
<span>Loading</span>
}

Disclaimer: The timer logic was not yet added to the core package at the time of writing this post. Since the compiler support was added it will not show compile or runtime exception. Probably, this logic will be available in Angular v17.

Using when (Imperative trigger conditions)

Apart from using built-in trigger conditions, the developer can use when to provide any custom logic to load the defer block. In the below code, canLoad method will return a boolean based on which the deferred content will be loaded.

export class AppComponent {
. . . . . .
public canLoad() {
return true;
}
}
@defer (when canLoad()) {
<app-defer></app-defer>
}

You can directly use signals , public property, or method in the when trigger condition. You can also use logical operators to load based on multiple criteria.

export class AppComponent {
showSignal = signal(true);
show = true;

public canLoad() {
return true;
}
}
@defer (when (canLoad() && showSignal()) || show) {
<app-defer></app-defer>
}

Can we use observables in the when trigger?

Update: I have reported this issue to the Angular team and it seems they have fixed it now. You can expect this to work in Angular v17. https://github.com/angular/angular/issues/52068

In Angular v17.0.0-next.6, I guess not, at the moment, passing observable to the when trigger, it always resolved as true . Using async pipe or toSignal also have no effect. Probably we can expect this in the main release or the Angular team might have a better explanation for this.

If you use async pipe, probably you will face the below error as I did.

@defer (when show | async) { // show = of(true)
<app-defer></app-defer>
} @placeholder {
<span>Loading</span>
}

Using prefetch

The developer can prefetch the deferred block chunk files before rendering them. The prefetch allows us to reduce the delay in loading the lazy chunk files which is required by the defer block.

The prefetch keyword is used to mention the prefetch condition in which the on and when can be used to mention the criteria required to prefetch the resource. A simple prefetch condition will look as below in which hovering the checkbox will prefetch the app-defer chunk file and will be rendered when the button is interacted.

<button #button>Load Defer Component</button>

<input type="checkbox" #check/>

@defer (on interaction(button); prefetch on hover(check)) {
<app-defer></app-defer>
}
prefetch — defer block

Mixing trigger conditions

You can use both on and when in a single defer block. All you need to ensure is that the on and when should be delimited by a semicolon ; as follows.

The trigger conditions between on and when are combined using OR logical operator.

@defer (on interaction(button),hover(check);
when (canLoad() && showSignal())) {
<app-defer></app-defer>
} @placeholder {
<span>Loading</span>
}

How to run the defer block in the Angular v17.0.0-next.6?

Check out the below blog post on how to enable and run the defer block example using Angular v17.0.0-next.6.

https://maddydeep28.medium.com/how-to-enable-the-new-control-flow-or-defer-block-in-the-angular-v17-0-0-next-6-6bbe29287cbe

Thanks for reading. If you like this article, please check out my articles here and follow me on Twitter(X) for more content like this.

Stackademic

Thank you for reading until the end. Before you go:

  • Please consider clapping and following the writer! 👏
  • Follow us on Twitter(X), LinkedIn, and YouTube.
  • Visit Stackademic.com to find out more about how we are democratizing free programming education around the world.

Published in Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.

Written by Madhu Sudhanan

Software developer and a blogger. Fond of Angular, React, Vue and Blazor frameworks. Follow me on Twitter — @maddydeep28. PortFolio — https://madhust.github.io/

Responses (3)

Write a response

Amazing, can’t wait to upgrade to v17!

Nice job breaking it down

Fricking amazing, thanks!