DEV Community

Suguru Inatomi
Suguru Inatomi

Posted on

Network-aware Preloading Strategy for Angular Lazy Loading

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 {}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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.

GitHub logo lacolaco / network-information-types

Type definitions for Network Information API

network-information-types

npm version

Type definition for Network Information API

Caveat

This is a temporary solution until TypeScript adds support for this API as built-in types. See https://github.com/Microsoft/TypeScript/issues/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
Enter fullscreen mode Exit fullscreen mode

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"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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 is separated See https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation#Browser_compatibility .

// Example: http://wicg.github.io/netinfo/#example-1
if (navigator.connection)
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
kioviensis profile image
Max Tarsis • Edited

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?

Collapse
 
lacolaco profile image
Suguru Inatomi

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.

Collapse
 
kioviensis profile image
Max Tarsis

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

Collapse
 
benjaminblouin profile image
Benjamin Blouin

Excellent

Collapse
 
amineamami profile image
amineamami

Excellent article, i never heard of this feature before.

Collapse
 
_rahulpandit profile image
Rahul Pandit

Nice article for network preloading ..

Collapse
 
ilwebdifabio profile image
Fabio Bedini whisher

Hi there,
excellent article but what's the reason
to pass route: Route as parameters if
it's never used?

Thanks in advance

Collapse
 
lacolaco profile image
Suguru Inatomi

It is pointless. It can be removed if you won't use it.

Thanks.