Performance optimization is a critical aspect of creating a successful Angular app. Slow loading times can lead to a frustrating user experience and ultimately drive users away. One common technique to improve performance is lazy loading, which allows the app to be broken down into smaller chunks that can be loaded on demand.
However, another important concept that is often overlooked is preloading strategies.
In this first article of the series "Load Less, Do More: A Guide to Angular Preloading Strategies", we will explore the world of preloading strategies and how they can be used to speed up the loading process and provide a smoother user experience. We will discuss what preloading strategies are, how they work, and the benefits they offer. To help illustrate these concepts, we will walk through an example of how to create a custom preloading strategy. By the end of this article, you will have a solid understanding of preloading strategies and how they can be applied to your Angular app.
Table of Contents
- Why performance optimization is important
- Why do preloading strategies matter?
- Default preloading strategies in Angular
- Using preloading strategies
- Implementing our own preloading strategy
- Takeaways
Why performance optimization is important
When creating web pages or online tools, performance optimization is a crucial aspect to provide a decent user experience, often referred to as "UX".
Do you remember the last time you were waiting for a web page to load? No matter what the site might have been, I doubt that you are remembering it as a pleasant experience.
In general, nobody likes to use a slow website as it can lead to frustration or even fear from the users browsing it ("Will it correctly saves my progress?", "Can I trust this website with my personal information?").
Ensuring that a website runs smoothly and loads quickly leads to a better UX overall.
Moreover, the speed of the app can impact how users perceive your brand: a slow and unresponsive site can create a negative impression, making users less likely to return.
Why do preloading strategies matter?
One of the first thing you fill hear about when seeking to improve the performances of your Angular app is lazy loading.
By splitting your application modules features into smaller chunks, you can load them on the fly instead of bundling them all together and force the user to download the whole website when accessing any page.
However, sometimes, you might know that, from a business perspective, a user will use some lazy modules when accessing a page. In that case, preloading them might make sense, to avoid an unnecessary latency to the user that will eventually want to see the page.
For those specific cases, Angular introduced the concept of preloading strategies.
A preloading strategy is a way to tell the Angular's router to load modules in the background, so that when the user navigates to a new page, the required modules have already been loaded and the page appears almost instantaneously.
A preloading strategy is a simple Angular class that extends the PreloadingStrategy
abstract class defined as such:
abstract class PreloadingStrategy {
abstract preload(route: Route, fn: () => Observable<any>): Observable<any>
}
When executed, this methods will return the call to fn
if the route
should be preloaded, or an Observable<null>
if not.
Default preloading strategies in Angular
NoPreloading
By default, when using lazy loading, all modules are lazily loaded.
This is achieve by enabling the NoPreloading
Strategy by default, which is described as:
Provides a preloading strategy that does not preload any modules.
Its usage will result in the loading of the targeted module only when requested:
Unfortunately, this can results in slower subsequent loading time as the user navigates through the app.
PreloadAllModules
It's opposite also exists as the PreloadAllModules
Strategy which will, as its name suggests it, preload everything:
Provides a preloading strategy that preloads all modules as quickly as possible.
Unlike the previous strategy, this time everything is loaded at once:
However, neither of those strategy might prove optimal if you want finer control on the way you load your Angular app.
For those cases, it is possible to create and use your own preloading strategy.
Using Preloading Strategies
In order to indicate which preloading strategy the router should apply, you need to provide it to the preloadingStrategy
property of the RouterModule.forRoot()
method in your app's root module:
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: /* ... */
})],
})
export class AppModule { }
However, if you are now using Standalone Components, you might want to use a provider to call during the bootstrap of your application.
This can be done by adding withPreloading
to the setup done in provideRouter
:
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes, withPreloading(/* ... */))
],
});
Note that if you write your own implementation of a preloading strategy, you will have to provide it on the root level too, so that it can be retrieved from the dependency injection container.
Implementing our own preloading strategy
We mentioned the importance of creating a custom preloading strategy sometime, let's see how to do it.
Purpose
Our custom preloading strategy will simply preload any lazy loaded route that has the flag preload
set to true
in its route data.
Setup
I will slightly modify the routes that I defined in the previous GIFs to add the flag:
export const routes: Route[] = [
{
path: "feature-1",
loadComponent: () =>
import("./app/feature-1/feature-1.component").then(
(m) => m.Feature1Component
),
// π This route should be preloaded
data: { preload: true },
},
{
path: "feature-2",
loadChildren: () =>
import("./app/feature-2/feature-2.route").then(
(m) => m.routes
),
},
];
With our custom PreloadingStrategy
, we should see that the "Feature #1" is loaded from the start but "Feature #2" is not.
Show me the code!
A preloading strategy is nothing more than a service that extends PreloadingStrategy
.
Knowing this, let's create our FlagBasedPreloadingStrategy
service:
// flag-based.preloading-strategy.ts
import { Injectable } from "@angular/core";
import { PreloadingStrategy, Route } from "@angular/router";
import { Observable } from "rxjs";
@Injectable({ providedIn: "root" })
export class FlagBasedPreloadingStrategy extends PreloadingStrategy {
// π For clarity, I prefer `load` rather than `fn` for the callback name
preload(route: Route, load: () => Observable<any>): Observable<any> {
return route.data?.["preload"] === true ? load() : of(null);
}
}
Notice how we provided it in
root
so that we can indicate the router to use it on bootstrap
The logic doesn't matter much here. We simply check the current route for the flag and, if present, we will load the module and otherwise we will not.
Looking great! We still have to indicate the router to use it tho. This can be done in the same way as the built in ones: by providing it during the bootstrap phase:
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes, withPreloading(FlagBasedPreloadingStrategy))
],
});
Now, if we open the network tab in the devtools, we should see that the feature #1 is loaded but feature #2 is not:
Similarly, if we now flag the feature #2 instead of the feature #1, we should see it loaded instead of the other:
to add the flag:
export const routes: Route[] = [
{
path: "feature-1",
loadComponent: () =>
import("./app/feature-1/feature-1.component").then(
(m) => m.Feature1Component
),
- data: { preload: true },
},
{
path: "feature-2",
loadChildren: () =>
import("./app/feature-2/feature-2.route").then(
(m) => m.routes
),
+ data: { preload: true },
},
];
Working as expected!
Takeaways
In this article, we saw what preloading strategies are and how they can help you to optimize the loading of your application to offer a smoother user experience by loading the required modules in the background, so the user doesn't have to wait for them to load when navigating to a new page.
By default, Angular provides two strategies:
- A
NoPreloading
Strategy that does not load any lazy route - A
PreloadAllModules
Strategy that does the opposite
However, in some business cases, you might want to orchestrate the loading of your lazy modules in a specific way. For such situations, implementing a custom preloading strategy might be the solution
If you would like to check the resulting code, you can head on to the associated
pBouillon / DEV.PreloadingStrategies
Demo code for the "Optimize your Angular app's user experience with preloading strategies" article
Optimize your Angular app's user experience with preloading strategies
Demo code for the "Optimize your Angular app's user experience with preloading strategies" article on DEV
In a future article on this series, we will see how to articulate guards and preloading strategies, stay tuned!
I hope that you learn something useful there!
Photo by Joey Kyber on Unsplash
Top comments (5)
ok
Nice one!
Very informative. Thanks
Thanks. I see it load the ts file. But how can we make initialize the component, eg. call the the constructor?
Your component's constructor is called whenever constructed.
To actually invoke the constructor, you must navigate to a page where your component is called.