DEV Community

Cover image for New Angular v17 feature: deferred loading
Gergely Szerovay for This is Angular

Posted on • Originally published at angularaddicts.com

New Angular v17 feature: deferred loading

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 imports 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 supported
  • how to show a placeholder, a loading state or an error state with additional @placeholder, @loading and @error blocks
  • how prefetching work

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

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 thrre different block types under the @defer block, so there is:

  • A @defer block, Angular renders it, when the isCheckedDefer signal's value becomes true. It contains a child component: &lt;app-c1/>
  • A @placeholder block, it's rendered initially, before the @defer block is triggered
  • When the @defer block is triggered, Angular loads the block's content from the server. During the loading, it shows the @loading block
  • If 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:

Image description

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:

Image description

Then the content of the @defer block, the &lt;app-a1> component is shown:

Image description

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:

Image description

Now we reload the app, clear the content of the Network tab, then block the network requests in the browser:

Image description

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:

Image description

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

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

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

@defer on timer

The on timer block is rendered after the specified time is elapsed:

@defer (on timer(5s)) {
  <span>Visible after 5s&lt;/span>
}
@placeholder {
  <span>Placeholder&lt;/span>
}
Enter fullscreen mode Exit fullscreen mode

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

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:

Image description

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):

Image description

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

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:

Image description

Then we click on the placeholder, and Angular renders the prefetched block (the <app-c3> component):

Image description

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!

Top comments (5)

Collapse
 
jdgamble555 profile image
Jonathan Gamble

Does v17 have standalone components with signals AND zoneless support?

Collapse
 
hakimio profile image
Tomas Rimkus

No, they postponed "signal components" for later releases. Most likely v18.

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Love this new feature,we are already using it. Amazing

Collapse
 
pbouillon profile image
Pierre Bouillon

This is great! That’s one more tool in the Angular toolbox I didn’t know I needed but will look like something I can’t code without in a couple of month

Thanks for the clear and exhaustive article!

Collapse
 
harnilpatel profile image
Harnil Patel

Angular 17 is gеnеrating a lot of buzz in thе dеvеlopmеnt community! Very well written.