New Angular 17 feature: deferred loading
Next level lazy-loading demonstrated by using a Signal-based and other examples
Angular 17 will be released in the beginning of November, and it has an exciting new feature called deferred loading (RFC).
Lazy loading is a technique that enables web apps to load resources (such as scripts) only when they are needed. Instead of loading all content upfront when the page initially loads, lazy loading defers the loading of non-essential content until the user interacts with the page, scrolls, or reaches a specific point in the page.
Lazy loading enhances the user experience, as it reduces the initial page load time and ensures that users can start interacting with the app as soon as possible, while non-essential parts of the app load in the background. It reduces the required bandwidth and the server load.
In the previous Angular versions, we can lazy-load a part of the application using the Router
, or we can use dynamic import
s and ngComponentOutlet
.
In Angular 17, the Angular team will push lazy-loading to the next level: Angular now has a @defer
control block enabling lazy-loading of the content of the block. Lazy-loading also applies to the dependencies of the content of the block: all the components, directives and pipes will be lazy-loaded, too.
In this article, I demonstrate the key aspects of lazy loading in Angular 17, such as
how we can specify a logical expression to trigger the rendering of a deferred block
how we can define a declarative trigger condition to trigger the rendering (for example
on hover
), which trigger types are supportedhow to show a placeholder, a loading state or an error state with additional
@placeholder
,@loading
and@error
blockshow prefetching work
This article is also available on dev.to with better source code syntax highlighting.
The full source code is available here:
https://github.com/gergelyszerovay/angular-17-deferred-loading
I used Angular v17.0.0-next.8 with standalone components and Signals. You can start the frontend by yarn run start
or npm run start
.
Using @defer with a logical expression
In the first example, I create a checkbox and bind it to the isCheckedDefer
signal. The signal's default value is false
, so initially the checkbox is unchecked, and the content of the @defer
block is not rendered. The examples below are from the src\app\app.component.html template file:
<div>
<input #checkboxDefer type="checkbox" [checked]="isCheckedDefer()" (change)="isCheckedDefer.set(checkboxDefer.checked)" id="checkboxDefer"/>
<label for="checkboxDefer">Open the network tab of the browser's developer tools, then check this checkbox to load the <strong>app-c1</strong> component</label>
</div>
<br>
@defer (when isCheckedDefer()) {
<app-c1/>
}
@placeholder {
<span>Placeholder</span>
}
@error {
<span>Error</span>
}
@loading(minimum 1s) {
<span>Loading...</span>
}
The @defer (when logical_expression) {
statement creates a @defer
block with the logical expression. I use the isCheckedDefer()
signal as a logical expression, as it evaluates to a boolean
value.
I added four three block types under the @defer
block, so there is:
A
@defer
block, Angular renders it, when theisCheckedDefer
signal's value becomestrue
. It contains a child component:<app-c1/>
A
@placeholder
block, it's rendered initially, before the@defer
block is triggeredWhen the
@defer
block is triggered, Angular loads the block's content from the server. During the loading, it shows the@loading
blockIf the loading fails, Angular shows the
@error
block
The use of the @placeholder
, @loading
and @error
blocks are optional, so we can use standalone @defer
blocks, too.
Let's see how this code works! When we open the app, the isCheckedDefer
signal is false
, so the @defer
block is not triggered and the content of the @placeholder
block is visible:
Open the Network tab of your browser's Developer tools, and clear it.
When we check the checkbox, the isCheckedDefer
signal becomes true, so Angular loads the content of the @defer
block. It removes the content of the @placeholder
block, and renders the content of the @loading
block. I specified a minimum
condition for this block, so it'll be visible for at least one second. It's also possible to specify an after
condition, it allows us to set a minimum duration time to wait before Angular shows the content of the loading block. If the content of the @defer
block is loaded during this duration, Angular does not show the content of the @loading
block.
So the content of the @loading
block is visible for one second:
Then the content of the @defer block, the <app-a1>
component is shown:
In the Developer tools we can see that after checking the box, Angular loaded a new chunk of the application, containing the content of the @defer
block:
Now we reload the app, clear the content of the Network tab, then block the network requests in the browser:
When we check the checkbox, the isCheckedDefer
signal becomes true
, so Angular loads the content of the @defer
block. It removes the content of the @placeholder
block, and renders the content of the @loading
block.
Then loading fails with a network connection error, so Angular shows the content of the @error
block:
Using @defer with a declarative trigger condition
Deferred blocks support the following declarative trigger types:
on interaction
on hover
on idle
on timer
on viewport
Let's create an example for all of these!
@defer on interaction
Angular renders the on interaction
block, when the user interacts with its @placeholder
block. An interaction can be a click, touch focus, or input events, like keypress
:
@defer (on interaction) {
<span>Clicked</span>
}
@placeholder {
<span>Placeholder (click on it!)</span>
}
@defer on hover
Angular renders the on hover
block, when the user hovers over its @placeholder
block:
@defer (on hover) {
<span>Hovered</span>
}
@placeholder {
<span>Placeholder (hover it!)</span>
}
@defer on idle
Angular renders the on idle
block, when the browser reaches an idle state after the page has been loaded:
@defer (on idle) {
<span>Browser has reached an idle state</span>
}
@placeholder {
<span>Placeholder</span>
}
@defer on timer
The on timer
block is rendered after the specified time is elapsed:
@defer (on timer(5s)) {
<span>Visible after 5s</span>
}
@placeholder {
<span>Placeholder</span>
}
@defer on viewport
Angular renders the on viewport
block, when the placeholder enters the browser's viewport:
@defer (on viewport) {
<app-c2 text="The block entered the viewport"/>
}
@placeholder {
<span>Placeholder</span>
}
After reloading the app, we can check using the inspector tool whether the content of the @placeholder
block has been rendered in the DOM or not:
Now follow these steps:
switch to the Network tab inside your browser's developer tools
clear the content of the Network tab
scroll all the way down to the bottom of the page
As a result, Angular loads and renders the content of the @defer
block (the <app-c2>
component):
Prefetching
Next to a trigger condition, we can specify an additional prefetch
condition:
@defer (on interaction; prefetch on hover) {
<app-c3/>
}
@placeholder {
<span>Placeholder (hover it, then click on it!)</span>
}
Reload the app, then follow these steps:
clear the content of the Network tab
hover over the placeholder in the “Prefetch“ section
As a result, Angular loads the content of the @defer
block, but it's not rendered, the @placeholder
remains visible:
Then we click on the placeholder, and Angular renders the prefetched block (the <app-c3>
component):
Summary
In this article, I demonstrated some of the great new features of Angular 17: I showed you how the new deferred blocks work and how to specify conditions to trigger the loading and rendering these blocks' content. I hope you have found my tutorial useful!
In the second part of this article series, I'll show you how the new control flow (@if
, @else
, @switch
and @case
blocks) works in Angular 17.
And as always, please let me know if you have some feedback!
👨💻About the author
My name is Gergely Szerovay, I work as a frontend development chapter lead. Teaching (and learning) Angular is one of my passions. I consume content related to Angular on a daily basis — articles, podcasts, conference talks, you name it.
I created the Angular Addict Newsletter so that I can send you the best resources I come across each month. Whether you are a seasoned Angular Addict or a beginner, I got you covered.
Next to the newsletter, I also have a publication called — you guessed it — Angular Addicts. It is a collection of the resources I find most informative and interesting. Let me know if you would like to be included as a writer.
Let’s learn Angular together! Subscribe here 🔥
Follow me on Substack, Medium, Dev.to, Twitter or LinkedIn to learn more about Angular!