Angular — Deferred Loading using defer block — What you need to know
Tags: angular, javascript, signals, deferredloading
Published: 2023-10-01 18:13:04

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://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.

```xml
@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.


![Lazy Load - Defer block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vk1aqemv06cigsrikmwn.png)


![Defer block - Lazy load chunk](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q94n3eydblsrrxfnt2nu.png)

## 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.

```xml
<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`, or `viewport` triggers. Now the placeholder block will act as a target for these triggers.

```xml
@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.

```xml
@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.

```xml
<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.

```xml
<input type="checkbox" #check/>

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

```xml
@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.

```xml
<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.

```xml
@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.

```xml
@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.

```xml
<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.

```xml
<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.

```xml
<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.

```xml
@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.

```xml
@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 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.

```typescript
export class AppComponent {
  . . . . . .
  public canLoad() {
    return true;
  }
}
```

```xml
@defer (when canLoad()) {
  <app-defer></app-defer>
}
```

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.

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

  public canLoad() {
    return true;
  }
}
```

```xml
@defer (when (canLoad() && showSignal()) || show) {
  <app-defer></app-defer>
}
```

## 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.

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

![Defer block - async pipe exception](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kij548iro3fymcj2eegm.png)

## 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.

```xml
<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 resource](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fy21w9612b5r1jhf9zgn.gif)

## 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.

```xml
@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](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](https://dev.to/madhust) and follow me on [Twitter(X)](https://twitter.com/maddydeep28) for more content like this.