How to lazy load large 3rd-party dependencies with Angular 17’s @defer

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:

  1. AnyChart for some Charts
  2. 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:

  1. Webpack Bundle Analyzer for the static build
  2. 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:

Base Build

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:

Base Metrics

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:

Deferred Charts Build

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:

Deferred Charts Metrics

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:

Deferred Pivot Table Build

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:

Deferred Charts Metrics

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

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.