In this guide, we'll introduce predictive prefetching in an Angular application. Let us get started!
Bootstrapping the Application
To bootstrap the application we're going to use Angular CLI. Make sure you have the latest version installed:
npm i -g @angular/cli
ng --versionThis guide uses Angular CLI 7.0.3.
After that run:
ng new guess-angularMake sure that during initialization you add Angular routing:
ng new guess-angular
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSSCreating an Application
As next step, let us define a few routes! Inside the app-routing.module.ts add the following configuration:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    loadChildren: './index/index.module#IndexModule'
  },
  {
    path: 'about',
    loadChildren: './about/about.module#AboutModule'
  },
  {
    path: 'example',
    loadChildren: './example/example.module#ExampleModule'
  },
  {
    path: 'media',
    loadChildren: './media/media.module#MediaModule'
  }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}We declare four routes, all of which we load lazily. For each route, declare the corresponding module and a component. For example, for the media route, we should have media.module.ts, which looks like this:
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MediaComponent } from './media.component';
@NgModule({
  declarations: [MediaComponent],
  imports: [
    RouterModule.forChild([
      {
        path: '',
        component: MediaComponent
      }
    ])
  ]
})
export class MediaModule {}And a media.component.ts:
import { Component } from '@angular/core';
@Component({
  selector: 'app-media',
  template: 'Media'
})
export class MediaComponent {}When you're ready, your src/app directory structure should look like follows:
.
├── about
│   ├── about.component.ts
│   └── about.module.ts
├── app-routing.module.ts
├── app.component.css
├── app.component.html
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── example
│   ├── example.component.ts
│   └── example.module.ts
├── index
│   ├── index.component.ts
│   └── index.module.ts
└── media
    ├── media.component.ts
    └── media.module.tsIn order to have a link to the MediaModule, set the template of the AboutComponent to:
About <a routerLink="/media">Media</a>Finally, update the AppComponent's template to:
<a routerLink="">Home</a>
<a routerLink="example">Example</a>
<a routerLink="about">About</a>
<br>
<router-outlet></router-outlet>Running the Application
Once you've defined all routes from above, start a development server:
ng serve 
Notice that while navigating in the application, each time when the user visits a page the browser sends a request for the corresponding JavaScript bundle. We observe this behavior because we're using lazy-loading for each route. Lazy-loading is a compelling technique that allows us to drop the size of the initial bundle. On the other hand, lazy-loading may also introduce latency when changing the page, if we haven't downloaded the bundles associated with the target route.
Predictive Prefetching
The introduced latency in the example above is negotiable because the bundles are tiny, but in a real-life application, the user would have to wait hundreds of milliseconds before the navigation completes. To address this issue, we can use prefetching. Prefetching allows us to fetch in advance resources which are likely to be needed shortly. For example, if we know that after being in the "Home" page the user is likely to visit "Example" we can download the JavaScript associated with "Example" while the user is still in the "Home" page.
Guess.js allows us to use prefetching by considering the user's navigational patterns extracted from an analytics report. For example, Guess.js can consume data from Google Analytics, build a machine learning model, and, at runtime, prefetch the resources which are likely to be needed next!
For simplicity in this guide, we're going to extract the report from a file, instead of using Google Analytics. In the root of your application, create a file called routes.json with the following content:
{
  "/": {
    "/example": 80,
    "/about": 20
  },
  "/example": {
    "/": 20,
    "/media": 0,
    "/about": 80
  },
  "/about": {
    "/": 20,
    "/media": 80
  },
  "/media": {
    "/": 33,
    "/about": 33,
    "/example": 34
  }
}This file specifies how many times the user has visited a given page from another. For example, if we look at the first property of the outermost object, we can see that from /, there were 80 sessions in which users have visited /example and 20 sessions in which users have visited /about.
Now let us extend the build of Angular CLI so we can use the Guess.js' webpack plugin!
Extending Angular CLI
To extend Angular CLI, we're going to use @angular-builders/custom-webpack as explained in this tutorial.
First, install @angular-builders/custom-webpack and @angular-devkit/build-angular:
npm i -D @angular-builders/custom-webpack @angular-devkit/build-angularAs next step, open angular.json and update the builder value from @angular-devkit/build-angular:browser to @angular-builders/custom-webpack:browser:
"architect": {
  ...
  "build": {
      "builder": "@angular-builders/custom-webpack:browser"
      "options": {
        ...
      }
    ...
  }
  ...
}As the next step, add a property with key customWebpackConfig to the options object residing in build:
"architect": {
  ...
  "build": {
      "builder": "@angular-builders/custom-webpack:browser"
      "options": {
        "customWebpackConfig": {
          "path": "./extend.webpack.config.js"
        }
      }
  ...
}As the final step let us configure the Guess.js webpack plugin.
Configure Guess.js
First, install Guess.js:
npm i -D guess-webpack guess-parserguess-webpack contains the Guess.js webpack plugin. guess-parser contains a collection of parsers which can statically analyze our Angular application to discover how the routes from the analytics source map to JavaScript bundles.
To use the Guess.js webpack plugin, create a file called extend.webpack.config.js in the root of the project and set the following content:
const { GuessPlugin } = require('guess-webpack');
const { parseRoutes } = require('guess-parser');
module.exports = {
  plugins: [
    new GuessPlugin({
      // Alternatively you can provide a Google Analytics View ID
      // GA: 'XXXXXX',
      reportProvider() {
        return Promise.resolve(JSON.parse(require('fs').readFileSync('./routes.json')));
      },
      runtime: {
        delegate: false
      },
      routeProvider() {
        return parseRoutes('.');
      }
    })
  ]
};In the snippet above, first, we import the GuessPlugin and the parseRoutes function. The parseRoutes function is responsible for creating the mapping between routes from our analytics source to the JavaScript bundles associated with them.
After that, we define our webpack configuration. Inside of it, we export an object with a plugins property. Here we add our GuessPlugin and we configure it by passing an object with three properties:
- reportProvider- returns analytics data that the- GuessPluginwould consume and build a machine learning model with
- runtime- the runtime configuration sets the- delegateproperty to- false. This setting means that we want to let Guess.js handle the bundle prefetching
- routeProvider- this method delegates its invocation to- parseRouteswhich returns the mapping between routes and JavaScript chunks
reportProvider, you can set the GA property with value your Google Analytics View ID. In this case, Guess.js will fetch data from your Google Analytics account and build the report automatically. For the purpose, you'll have to provide a read-only access to your view.
That's it! Now let us build the application and see the result:
npm run build
cd dist/guess-angular && serve -s .On the image below, we can see the prefetching logic that Guess.js added to the application:
 
When the user navigates from "Home" to "Example," the browser provides the "Example" bundle from the disk instead of fetching it from the network. We observe this behavior because when the user initially visits the "Home" page, Guess.js prefetches the bundle associated with the "Example" page.
Same happens when the user goes from "About" to "Media" since Guess.js prefetches the "Media" bundles when the user initially visits "About."
GuessPlugin which built a model from a sample report that we provided from the disk.
Finally, we observed Guess.js' prefetching behavior at runtime.