Introduction
In Angular 17, a new feature was added that allows for fine grained control over when components will be loaded in your app. There's no longer a need for complicated workarounds to load individual components onto the screen. This control will allow you as a developer to fine tune the performance of your app and how much information is downloaded to your user's phone or computer when they use your application. In this article, we'll briefly go over how to use the new defer blocks, but more importantly we'll look at the differences in your app's bundle sizes with and without the deferred views.
How Deferred Views Work in Angular
Before we jump in, let's look at a couple things related to how deferred views work in Angular. This won't be a deep dive into the internals, but should give you more insight into how they work.
First, using deferred views in your Angular app changes the type of import of the deferred component from a static import to a dynamic import. Static imports are a feature of JavaScript in which all the necessary code is downloaded and executed before the app can run. Dynamic imports allow you to request a module that you need at any point in your application.
Another piece to understand is the difference between lazy loading and lazy initialization. You can see some discussion on Twitter about this here, but briefly lazy initialization only postpones the instantiation of the template (like when you use ng-template). The code for the component is still loaded, it's just not painted to the screen immediately. Lazy loading postpones the loading of the component and the execution of the code in the component until it's needed.
How to Use Deferred Views
Using deferred views in your Angular app is pretty straight forward: create an app on v17, or update your app to v17, when wrap the component you want to defer in a defer block:
@defer {
<app-my-component></app-my-component>
}
That's all it takes to use the new deferred blocks in your app. Of course, you can get more control by adding some conditions after the @defer and before the curly brackets. The triggers that Angular provides are as follows:
- On idle
- On viewport
- On interaction
- On hover
- On immediate
- On timer
When the condition is met, the component will be added to the DOM and will display as you are expecting. You can mix and match the triggers to make more complex conditions if needed.
There is more you can do with deferred views, such as placeholder blocks, loading blocks, and error blocks, but I will not cover that in this article. Refer to the docs for more in depth information about deferred views.
Bundle Size Effects of Deferred Views
The main reason for using deferred views in our applications will be to increase performance of the app, which is heavily impacted by the size of the bundle of the application. If your initial bundle size is large, it takes longer to download and start on the user's device. The effect is exacerbated by slow networks or less powerful devices. We have some ways to decrease the bundle size, such as lazy loading routes, but this is one more tool in our toolbelt.
Let's look at a sample application and the effect that deferred views have on a single, small component. For this demo, I created a brand new application with Nx on Angular v17. I used esbuild, Tailwind, and standalone components. I then created a new component. The new component doesn't do anything and the only HTML it has is the default text that is added for a new component. Here is the bundle size of the application when the component is added to the page normally, with no deferred loading.
Because the application isn't really doing anything, the bundle sizes are already really small. But look at the size of the main chunk after wrapping our new component in a defer block:
The main chunk becomes much smaller, now at just 82.54 kB instead of 181.66. Now, there are a couple other chunks as well, including the lazy chunk, but we can see that 411 bytes will be deferred until the user needs that information.
This is interesting to see, especially if the deferred component is something that may not be used by every user. If it's only going to be loaded occasionally, or on certain interactions, we can still save the user some bandwidth and increase the performance of the application.
Also as a reminder, this component is tiny; it's not doing anything. Let's try adding a little more content to the component, like a really big list that it'll loop over on the screen. I'm going to create an array of 1000 items that are looped over and displayed on the screen, with those 1000 items being hard coded in the component. Here's the bundle size when that component is not deferred.
The main chunk is now 193.1 kB, as opposed to 181.66 initially. And now here are the sizes with the deferred component.
When the component is deferred, the main chunk is the same size as before even though there are 1000 items inside the component now. The lazily loaded chunk is obviously bigger now, and the top initial chunk is a little bigger than before also.
Potential Gotchas
Using deferred views in Angular is easy, as demonstrated above, but you can run into some issues that you're not expecting if you're not careful. One of those things to remember is that your component will only be lazily loaded (as the demo above) if you aren't eagerly loading the component somewhere else.
So, for example, let's pretend you have a contact form component, and it shows up on the contact page and the cart page, but it's deferred on the cart page. For this demo, let's also assume the contact and cart components are in the same module. Because the contact component is eagerly loaded in the contact page, it will not be lazily loaded in the cart page. This makes sense, and may seem obvious, but it's important to point it out so you don't expect the size of your bundles to change in a situation like I've spelled out here.
Conclusion
Deferred views in Angular are poised to be one of the best features of an already great framework. The performance improvements and the ease of implementation will make it easy for developers of all skill levels to implement. And as we can see above, there are definite bundle size improvements, and that's with extremely simple examples as well. As your component increases in complexity, there is a good chance the savings would be more dramatic.