Besides some minor updates concerning SSR, the most significant upgrade for us performance enthusiasts in Angular 17 is undoubtedly the new block template syntax, including the defer block feature.
In our recent performance blog post on improving initial load performance with Angular 17's deferrable views, we introduced the new code>@defer feature and its syntax.
Deferring is an elegant and intuitive option to delay the loading of large 3rd-party dependencies until they are needed. This blog post will guide you on how to implement this quick-win initial load optimization task.
Case Study with 2 examples
A few months ago, we blogged about why initial load performance is crucial, focusing on SSR. A few weeks later, we introduced Angular 17's Deferrable Views. This time, we want to demonstrate how to quickly improve the loading of Angular apps that incorporate large 3rd-party dependencies by using standalone components and @defer
.
There are numerous reasons why developers might have to use such a library, which might not even be tree-shakable. However, let's not delve into those discussions. Instead, we aim to focus on how to use @defer to easily lazy-load such heavy-weighted (and, therefore, resource-intensive) dependencies.
In our example we're using two large dependencies:
- AnyChart for some Charts
- Flexmonster for a Pivot Tables
You can download and run the demo from this GitHub repo: https://github.com/L-X-T/ng-defer-large-deps-demo. Please checkout the main
branch for the base case of our demo.
By the way, we're using the vendor: true
flag for building to create a separate vendor bundle
for the 3rd-party dependencies. The main bundle
will contain only the project's source, and both bundles will be eagerly loaded upon bootstrapping the Angular app.
Furthermore, we'll use two tools to compare the builds and the initial load performance without and with deferring:
- Webpack Bundle Analyzer for the static build
- Chrome Lighthouse for some initial load performance metrics
Base Build
Now we'll run Webpack Bundle Analyzer (WBA) for the base i.e. our main
branch:
You can see that the vendor bundle
takes up like 99% of the build size. It's 4.81MB. This is because the anychart
und the flexmonster
imports are not tree-shakable, and thus, the full libraries will land in our Angular build.
Base Metrics
Now we'll run Chrome Lighthouse for the base, running the mobile performance test:
You can see that the performance metrics are not ideal because the browser needs to load the large vendor bundle
to fire up our app.
If you're not familiar with these metrics, make sure to check out our post about how to measure initial load performance.
Now we're going to improve the initial load by deferring the charts and the pivot table.
Deferring the Charts
Let's start with the charts. In this first example we use some different on
triggers for demonstration.
You can find the commit with all the changes in the deferred-charts
branch.
Here is an example:
@defer (on viewport) {
<app-chart [id]="chart.id" [data]="chart.data" />
} @placeholder {
<p>Chart is loading immediately.</p>
}
Deferred Charts Build
Let's take a look at the updated build analyzer result:
You can see that a new lazy chunk 498
including anychart
. The vendor chunk
size was reduced to 2.39MB.
Deferred Charts Metrics
Now we'll run Chrome Lighthouse again:
The performance metrics have improved significantly but are still not entirely satisfactory.
Deferring the Pivot Table
Let's continue with the pivot table.
You can find the new changes in the deferred-pivot-table
branch.
Here is the @defer
block for the pivot table:
@defer (on immediate) {
<app-pivot-table />
} @placeholder {
<p>Pivot table is loading immediately.</p>
}
Deferred Pivot Table Build
Let's take another look at the final build analyzer result:
You can see that another lazy chunk 595
including flexmonster
was created. The vendor chunk
size was again reduced down to 0.27MB!
Deferred Pivot Table Metrics
Now we'll run Chrome Lighthouse one more time:
Finally, the performance metrics have improved further and are now acceptable; some of them are even good.
Conclusion
Angular 17's introduction of Deferrable Views, particularly the new @defer block syntax, marks a significant leap in simplifying the dynamic loading of standalone components. @defer not only streamlines the process but also enhances Initial Load Performance by deferring heavy components, such as those with large third-party packages, until they are needed.
In this post, we've demonstrated how easy it is to implement an initial loading quick-win by applying @defer when dealing with very large 3rd-party dependencies.
References
- What's new in Angular 17 by Manfred Steyer
- Introducing Angular 17 by Minko Gechev
- Deferrable Views with Jessica Janiuk on Angular YouTube
- Deferrable Views in the Angular Docs
Performance Deep Dive Workshop
If you want to deep dive into Angular performance, we offer a dedicated Performance Workshop 🚀 - both in English and German.
This blog post was written by Alex Thalhammer. Follow me on GitHub, X or LinkedIn.