Predictive Preloading Strategy for Your Angular Bundles
Tags: angular, webdev, typescript, javascript
Published: 2019-05-31 08:04:30

---
title: Predictive Preloading Strategy for Your Angular Bundles
published: true
tags: angular, webdev, typescript, javascript
description: Learn how to create your own custom Angular preload strategy to preload bundles before a user navigates to their route
cover_image: https://thepracticaldev.s3.amazonaws.com/i/n1ihp8r9jkepfm8rvvp5.jpg
series: Angular Preload Strategies
canonical_url: https://johnpapa.net/predictive-preloading-of-your-angular-bundles/
---

Users want fast apps. Getting your JavaScript bundles to your browser as quickly as possible and before your user needs them can make a huge and positive impact on their user experience. Knowing how you can improve that experience is important.

One way you can improve user experience with your Angular apps is to strategically decide which bundles to preload. You control when your bundles load and which bundles load. This is why you should explore choosing a built-in or creating your own custom Angular preload strategy.

In this series we'll explore a few of your options for preloading Angular bundles.

Here are the articles in this series

- [Preload all Angular Bundles](https://dev.to/john_papa/preload-all-angular-bundles-1b6l)
- [You Pick Which Angular Bundles to Preload](https://dev.to/john_papa/you-pick-which-angular-bundles-to-preload-5l9)
- [Preload Angular Bundles When Good Network Connectivity is Detected](https://dev.to/azure/preload-angular-bundles-when-good-network-connectivity-is-detected-j3a)
- [Predictive Preloading Strategy for Your Angular Bundles](https://dev.to/john_papa/predictive-preloading-strategy-for-your-angular-bundles-4bgl)

## Scouting Ahead

The "on demand" strategy preloads one or more routes when a user performs a specific action. You decide which action will cause a route to preload. For example, you could set this up to preload a route while a user hovers over a button or menu item.

You can create the custom `OnDemandPreloadService` by creating a class that implements the `PreloadingStrategy` interface, and providing it in the root. Then you must implement the `preload` function and return the `load()` function when you want to tell Angular to preload the function.

Notice the `preload` function in the class `OnDemandPreloadService` examines the Observable `preloadOnDemand$`. It pipes the observable and uses the `mergeMap` RxJs operator to switch to a new Observable. This new Observable's value depends on the local `preloadCheck` function.

The `preloadCheck` function checks if the `preloadOptions` (which comes from the original Observable) has a `routePath` that matches a route that has the `data.preload` property set to `true`. So here we are opting some of the routes into preloading and leaving some routes to be loaded when they are requested explicitly.

```typescript
@Injectable({ providedIn: 'root', deps: [OnDemandPreloadService] })
export class OnDemandPreloadStrategy implements PreloadingStrategy {
  private preloadOnDemand$: Observable<OnDemandPreloadOptions>;

  constructor(private preloadOnDemandService: OnDemandPreloadService) {
    this.preloadOnDemand$ = this.preloadOnDemandService.state;
  }

  preload(route: Route, load: () => Observable<any>): Observable<any> {
    return this.preloadOnDemand$.pipe(
      mergeMap(preloadOptions => {
        const shouldPreload = this.preloadCheck(route, preloadOptions);
        return shouldPreload ? load() : EMPTY;
      })
    );
  }

  private preloadCheck(route: Route, preloadOptions: OnDemandPreloadOptions) {
    return (
      route.data &&
      route.data['preload'] &&
      [route.path, '*'].includes(preloadOptions.routePath) &&
      preloadOptions.preload
    );
  }
}
```

## Route Definitions

This strategy requires that you indicate which routes can be preloaded. You can do this by adding the `data.preload` property and set it to `true` in your route definition, as shown below.

```typescript
export const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'heroes' },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('app/dashboard/dashboard.module').then(m => m.DashboardModule),
    data: { preload: true }
  },
  {
    path: 'heroes',
    loadChildren: () =>
      import('app/heroes/heroes.module').then(m => m.HeroesModule),
    data: { preload: true }
  },
  {
    path: 'villains',
    loadChildren: () =>
      import('app/villains/villains.module').then(m => m.VillainsModule)
  },
  { path: '**', pathMatch: 'full', component: PageNotFoundComponent }
];
```

Notice that the dashboard and heroes routes both have the `preload.data` property set to `true`. However, the villains route does not have this property set. In this scenario the heroes and dashboard have preloading enabled, but the villains would only load when the user navigates to this route.

## Setting the Custom OnDemandPreloadService

Then when setting up your `RouterModule`, pass the router options including the `preloadingStrategy` to the `forRoot()` function.

```typescript
@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      preloadingStrategy: OnDemandPreloadStrategy
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}
```

## Deciding When to Preload

The missing piece here is the mechanism that you use to tell the app which route to preload and when to preload it. Notice the service `OnDemandPreloadService` in the code below. You can call this service's `startPreload` function and pass the route you wish to preload. The `OnDemandPreloadService` service then next's the subject (think of this like publishing or emitting a message). Then whoever or whatever listens to that message can act on it.

This is where the `OnDemandPreloadStrategy` strategy comes in, as it is listening.

```typescript
export class OnDemandPreloadOptions {
  constructor(public routePath: string, public preload = true) {}
}

@Injectable({ providedIn: 'root' })
export class OnDemandPreloadService {
  private subject = new Subject<OnDemandPreloadOptions>();
  state = this.subject.asObservable();

  startPreload(routePath: string) {
    const message = new OnDemandPreloadOptions(routePath, true);
    this.subject.next(message);
  }
}
```

## Bind to a Mouseover Event

Now your app is ready to preload a route when you decide to do it. You can try this by binding a DOM event such as `mouseover` and firing the `OnDemandPreloadService`'s `startPreload` function.

```html
<a
  [routerLink]="item.link"
  class="nav-link"
  (mouseover)="preloadBundle('heroes')"
  >heroes</a
>
```

Notice the following code accepts the route path and passes it along to the `preloadOnDemandService.startPreload` function.

```typescript
preloadBundle(routePath) {
  this.preloadOnDemandService.startPreload(routePath);
}
```

## All Together

Let's step back and follow how this all works.

1. A user hovers over your anchor tag
1. The `mouseover` binding calls a function in your component, passing the route path ('heroes' in this case)
1. That code calls the `PreloadOnDemandService` service's `startPreload`, passing the route path to it
1. The `PreloadOnDemandService` service next's the RxJS Subject, which is exposed as an Observable
1. The `OnDemandPreloadStrategy` gets a handle on that Observable, and it knows when it "nexts"
1. The `OnDemandPreloadStrategy` pipes it into `mergeMap` and evaluates the route for preloading
1. If it decides to preload, the `OnDemandPreloadStrategy` returns a new Observable with the `load()` function
1. If it decides not to preload, the `OnDemandPreloadStrategy` returns an Observable with the `EMPTY` observable (which does not preload)
1. The Angular router listens to the response of the strategy's `preload` function and either preloads or not, accordingly.

## Try It

After applying this strategy, rebuild and run your app with `ng serve`. Open your browser, open your developer tools, and go to `http://localhost:4200`. When you inspect the Network tab in your browser you will likely see none of your bundles already preloaded (except whichever route your navigated to by default, if that was lazy loaded).

Then hover over the HTML element where it fires with the `mouseover` event you bound. Check your network tab in your browser and you will see the bundle will be preloaded.

## Deciding What Is Right For Your App

Now that you know how to create your own preload strategy such as `OnDemandPreloadService`, how do you evaluate if this is the right strategy for your app?

This is a more involved strategy for certain. Could it be beneficial to your users? Do your users often hover over search results before selecting them? Would that normally fire off a lazy loaded bundle? If so, perhaps this could give that preload a jumpstart.

If you can determine that your users' behavior and workflow often follows a specific path before loading a new bundle, then this strategy could be beneficial.

You can apply this to a number of scenarios such as hovering over an HTML element, clicking a button, or scrolling to a specific area of the screen.

In the end the decision is up to you. I recommend before choosing this options, or any preload strategy, that you test at various network speeds under various valid and common user workflows. This data will help you decide if this is the right strategy for you, or if another may be more beneficial for users of your app.

## Resources

- Get [VS Code](https://code.visualstudio.com/?wt.mc_id=devto-blog-jopapa)
- Get the VS Code [Angular Essentials](https://marketplace.visualstudio.com/items?itemName=johnpapa.angular-essentials&wt.mc_id=devto-blog-jopapa)
- Get the VS Code [Angular Snippets](https://marketplace.visualstudio.com/items?itemName=johnpapa.angular2&wt.mc_id=devto-blog-jopapa)