Angular library lazy loading — part 2
In a previous article, we talked about how Angular does bundle and load chuncks of a library, depending on how we organise the code inside and how we expose it outside.
This article should be shorter, and we will simply take a look at the latest (at the time this article is writen) versions of Angular compiler, meaning esbuild
and application
.
Starting with a multiple feature library
Let’s take the existing application that was created with Angular 16 and move up to v17 :
First, let’s change the builder of our Angular application, you could either use esbuild
or application
, it won’t make much of a difference, since we have a very simple application. The dist
folder is a little bit different with application
builder though, it’s useful to notice when running our application on static generated sources.
"builder": "@angular-devkit/build-angular:browser-esbuild",
With the same objective as before, let’s create the features in the core library and see how our bundle works. We start with standalone features and therefore no NgModule, so, our library will look like this :
The routing in our application will look like this :
This configuration is very similar to the previous one, minus the modules, and the compiler does reflect that, with the lazy chunk files like this :
So, obviously, no difference at all, except a minor improvement in bundle size, compared to the simple browser
builder. The whole code of the library is still bundled together, even if we declare lazy loaded features.
Secondary entrypoints
Now, let’s split the code like we did before, using the secondary entrypoints of the library. If you don’t know how to do it, check the previous article. In the end of refactoring, we should have a clean bundle separation like this :
Our 2 paths for the features routing look now like that :
Access secondary entrypoint code
Of course we want to check the code interactions that we already know can alter the bundle. We do exactly the same operation as before : add a bills service, expose it and consume it in the main app.component
code. And then display the number of pending bills right in the main page header.
We notice now that the chunk still exists, as soon as we access the route, we can see that it’s loaded :
All seems to be okay, even with code adherence. But if we take a closer look, we see that something is not right with the size of the bundle :
Obviously the bundle is much smaller than the original, what’s up with this one ? Let’s see the content of the bills chunk :
It’s just a shell, asking for some already loaded chunk js file ! This file is only present when the adherence is present, and it’s pre-loaded as initial chunk file :
And the content of this chunk is probably what we would expect from a lazy loaded feature :
So, basically, the new builder is able to separate the code from the main bundle, unlike the basic browser
older builder. But it still has to load this code before it’s even needed.
The same operation as before will result in the expected outcome : move the service in the main entrypoint of the library, and use it where ever we want : either from the application or a secondary entrypoint.
Now everything is back to optimal situation with the chunks :
Conclusion
In a nutshell, the advice from the previous article remains, if we want routed lazy loaded features in our Angular library, we need to separate the code that is eagerly loaded from the code that lazily loaded. Any adherence will bring back the code in the initial bundles.