DEV Community

Cover image for Angular — Deferred Loading using defer block — What you need to know
Madhu Sudhanan P
Madhu Sudhanan P

Posted on • Originally published at maddydeep28.Medium

Angular — Deferred Loading using defer block — What you need to know

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 importand 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://dev.to/madhust/lazy-loading-non-routable-angular-modules-imperative-declarative-pattern-3a33

The previous solution I used has some pitfalls and it’s not a 100% declarative way. But with the new @defer block 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>
}
Enter fullscreen mode Exit fullscreen mode

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.

Lazy Load - Defer block

Defer block - Lazy load chunk

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>
}
Enter fullscreen mode Exit fullscreen mode

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, or 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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode
@defer (when showSignal()) { // showSignal is a signal.
  <app-defer></app-defer>
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

immediate

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

@defer (on immediate) {
  <app-defer></app-defer>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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 exceptions. 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, the canLoad method will return a boolean based on which the deferred content will be loaded.

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

You can directly use signals, public property, or methods 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;
  }
}
Enter fullscreen mode Exit fullscreen mode
@defer (when (canLoad() && showSignal()) || show) {
  <app-defer></app-defer>
}
Enter fullscreen mode Exit fullscreen mode

Can we use observables in the when trigger?

I guess not, at the moment, passing observable to the when trigger, it always resolved as true. Using async pipe or toSignal also has 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>
}
Enter fullscreen mode Exit fullscreen mode

Defer block - async pipe exception

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>
}
Enter fullscreen mode Exit fullscreen mode

Prefetch defer block resource

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 the logical OR operator.

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

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.

Top comments (0)