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.
@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.
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
, 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>
}
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.
immediate
idle
-
interaction
/hover
viewport
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
andkeydown
. 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 exceptions. Probably, this logic will be available inAngular 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;
}
}
@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.
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?
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>
}
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>
}
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>
}
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.
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)