This post explains how to make network-aware preloading strategy for lazy loading of Angular Router.
It can improve user experience with lazy loading despite users network condition.
What is Preloading?
Preloading is an important feature of Angular Router's lazy loading. This is available since 2.1.0.
By default, when the application uses lazy loading with loadChildren
, chunked lazy modules will be loaded on-demand. It can reduce initial bundle size but users have to wait for loading of chunks on transition.
Preloading changes that. By preloading, the application will start loading chunked modules before needed. It can improve user experience with smooth transition.
Here is the best article to read at first about preloading in Angular by Victor Savkin. He is the author of the feature.
Angular Router: Preloading Modules
Preloading Strategy
Angular Router supports customizing preloading behavior with PreloadingStrategy
feature. There are two built-in strategies; PreloadAllModules
and NoPreloading
.
NoPreloading
is the default behavior that doesn't preload any modules.
PreloadAllModules
loads all lazy modules immediately after bootstrapping. In other word, this is "As soon as possible" strategy.
import { RouterModule, NoPreloading, PreloadAllModules } from '@angular/router';
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules, // or NoPreloading
}),
],
})
class AppRoutingModule {}
PreloadingStrategy
is a simple class object implementing a preload
method. So we can make custom preloading strategy in ease like below.
The preload
method takes two arguments; route
and load
. route
is a route object that you declare in routes
array. load
is a function that trigger loading a module.
// custom-preloading-strategy.ts
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, EMPTY } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (shouldPreload(route)) {
return load();
} else {
return EMPTY;
}
}
}
// app-routing.module.ts
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: CustomPreloadingStrategy,
}),
],
})
class AppRoutingModule {}
Preloading Problem: Cost of networking
Preloading can improve user experience, but it is only in the case the device uses in fast network enough. Sometimes mobile devices have a narrow-band network connection. If then the application tries to preload all modules ASAP, it affects other connections like AJAX in a bad way.
Preloading is an appropriate solution for users who has a strong network. If they don't, on-demand loading is better. But this condition can change very dynamically, so the application have to get network information in runtime and turning on/off preloading.
I call that "Network-aware Preloading Strategy".
Using Network Information API
Network Information API is a new Web standard API proposal. The Network Information API provides information about the system's connection.
The entire API consists of the addition of the NetworkInformation
interface and a single property to the Navigator
interface: Navigator.connection
. Because this API is not a standard yet, TypeScript doesn't have its type definition. So I've created that as network-information-types
package and it is used in all example codes below.
lacolaco / network-information-types
Type definitions for Network Information API
network-information-types
No Longer Maintained
TypeScript supports Network Information API natively since version 4.4. Just use dom
or worker
library.
Type definition for Network Information API
Caveat
This is a temporary solution until TypeScript adds support for this API as built-in types. See microsoft/TypeScript#27186 .
Usage
- Install Package via npm
- Edit your tsconfig.json
- Now you get
navigator.connection
with its type!
Install
$ yarn add -D network-information-types
tsconfig.json
network-information-types
is a ambient types that modify global navigator
type, so it MUST be added in types
.
Package names in types
array are resolved with typeRoots
. By default, typesRoots
is just ./node_modules/@types
.
To resolve network-information-types
package, add the relative path directly as below.
{
"compilerOptions": {
...
"types": [
"./node_modules/network-information-types"
]
},
}
Use the types
Now you can access navigator.connection
property as NetworkInformation
object.
navigator.connection
and its properties are all optional because browser support for each…
Making Network-aware PreloadingStrategy
Let's make network-aware preloading strategy with Network Information API! The following code defines shouldPreload
function that is used in the above CustomPreloadingStrategy
example.
navigator.connection
is landed in limited browsers. So we MUST detect the feature. In this case,
export function shouldPreload(route: Route): boolean {
// Get NetworkInformation object
const conn = navigator.connection;
if (conn) {
// With network information
}
return true;
}
Detecting "Save Data" mode
At first, "Save Data" mode should be prioritized the best. It means the user strongly cares about payload size for their cost- or performance-constraints. Use NetworkInformation.saveData
property and return false
.
export function shouldPreload(route: Route): boolean {
// Get NetworkInformation object
const conn = navigator.connection;
if (conn) {
// Save-Data mode
if (conn.saveData) {
return false;
}
}
return true;
}
Detecting "2G" connection
Network Information API can recognize the network's effective connection type; 4G, 3G, 2G, and Slow 2G.
In this sample, the application disables preloading when the user is in 2G network.
export function shouldPreload(route: Route): boolean {
// Get NetworkInformation object
const conn = navigator.connection;
if (conn) {
// Save-Data mode
if (conn.saveData) {
return false;
}
// 'slow-2g', '2g', '3g', or '4g'
const effectiveType = conn.effectiveType || '';
// 2G network
if (effectiveType.includes('2g')) {
return false;
}
}
return true;
}
Network Information API has also several other properties like rtt
(RTT, round-trip time of the connection). You can add more checks for your application.
Conclusion
- Angular Router is supporting preloading feature since 2.1.0.
- You can create your own custom preloading strategy
- Preloading is effective only for users with a fast network.
- Network Information API is available in several browsers.
- It's very easy to make network-aware preloading strategy.
Thank you for reading!
Top comments (8)
Awesome!
A good usage example of that strategy
Btw, have you heard about RouteReuseStrategy?
Do you have some useful (real-world) examples when we may need to customize it?
yes, I sometimes use it to prevent reusing compoments by router.
Reusing is for good user experience by transition quickly. But it makes component's state more complex. Components need to be initialized not ngOnInit but on route change.
In my opinion, for component's maintainability, sometimes it may be reasonable that stops reusing.
Are you talking about more a deep level of maintainability?
I mean there is no difference inside component depending on the route reuse strategy.
I have just heard only that router caches some component's data and after the navigation, it leaves detached nodes.
And as you said above, sometimes we don't need it, so it's good that we have a possibility to simplify the flow
Excellent
Excellent article, i never heard of this feature before.
Nice article for network preloading ..
Hi there,
excellent article but what's the reason
to pass route: Route as parameters if
it's never used?
Thanks in advance
It is pointless. It can be removed if you won't use it.
Thanks.