Service Workers in Angular With @angular/service-worker

@angular/service-worker is a new core package available for Angular that gives us an abstraction layer for configuring and adding a service worker to your apps and achieve offline capabilities. It allows to concentrate on configuration instead of manually coding the service worker.

In this post we’ll explore the basics of setting up and using a service worker in an Angular 5+ app.

You may want to read over our intro if you're new to the concept of service workers and Progressive Web Apps.

Setup

Let’s breakdown the setup into a few different options:

  • New or existing Angular app that uses the Angular CLI < 1.6
  • Existing Angular app that uses Angular CLI >= 1.6
  • New Angular app using Angular CLI >= 1.6

Angular CLI 1.6 is still in beta at the time of this writing.

New or existing Angular app that uses the Angular CLI < 1.6

First, you’ll want to add the @angular/service-worker package to your project:

$ npm install @angular/service-worker

# or, using Yarn:
$ yarn add @angular/service-worker

Then, since the Angular CLI < 1.6 doesn’t have support for the service worker package, things get a little more tricky and we need to manually perform a few operations upon building the app to properly setup the service worker. Here’s a snippet from Maxim Salnikov with npm scripts that you can add to your package.json file:

These scripts take care of generating the ngsw.json file in the dist folder and copying over the service worker script itself (ngsw-worker.js) in the dist folder as well.

Now you can use npm run build-prod-ngsw to build your app for production and setup the service worker. Or you can use npm run serve-prod-ngsw to build for production and serve the build on port 8080 using http-server.

Existing Angular app that uses Angular CLI >= 1.6

If your existing app is using Angular CLI v1.6+, you can add service worker capabilities by installing @angular/service-worker

$ npm install @angular/service-worker

# or, using Yarn:
$ yarn add @angular/service-worker

And then enabling a configuration using the CLI:

$ ng set apps.0.serviceWorker=true

This configuration command adds the following to the CLI configuration file:

.angular-cli.json

...,
"apps": [
  {
    ...,
    "serviceWorker": true,
    ...
  }
],
...

New Angular app using Angular CLI >= 1.6

If you’re using the Angular CLI v1.6+ and starting a new Angular project, you’re in luck because you can simply specify a flag to add service worker capabilities to your app and the package and configuration will be set automatically:

$ ng new best-todo-app --service-worker

NgModule Configuration

No matter which option you followed to setup @angular/service-worker in your project, the configuration setup in your app module will be the same and looks like this:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    environment.production
      ? ServiceWorkerModule.register('/ngsw-worker.js')
      : []
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

This will take care of registering the service worker if the app is in production.

Note that this also means that you'll always have to test out your service worker in your production app.

Configuration

Now that we have the basic building blocks in place, we can get started with the real meat of the matter: the service worker configuration file. The convention is to create a ngsw-config.json file in your app’s src directory.

Here’s the default content of that file when created using the Angular CLI 1.6+:

ngsw-config.json

{
  "index": "/index.html",
  "assetGroups": [{
    "name": "app",
    "installMode": "prefetch",
    "resources": {
      "files": [
        "/favicon.ico",
        "/index.html"
      ],
      "versionedFiles": [
        "/*.bundle.css",
        "/*.bundle.js",
        "/*.chunk.js"
      ]
    }
  }, {
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {
      "files": [
        "/assets/**"
      ]
    }
  }]
}

In this default configuration the following top level configurations are in place:

  • index points to your app’s index.html file.
  • assetGroups is for named groups of assets to be cached at build time and that form the app shell.
  • Here we have two named asset groups: app and assets.
  • In the app asset group, the static favicon.ico and index.html files are included as well as globs for versioned JavaScript and CSS bundles. The assets in that group are prefetched because they are needed for the barebones app shell to work offline.
  • In the assets asset group, we lazily cache any other static assets within the project’s /assets folder as they are loaded using, thanks to "installMode": "lazy".

This basic configuration will work for a barebones Angular app, but as we start building our app we’ll make use of external resources and populate data from external API calls. Ideally we’d like to have that data cached as well, to ensure a smooth offline experience and enhanced performance.

External resources that are needed as part of the app shell can be added to a named assetGroup using the url key with configuration similar to the following:

{
  "name": "assets",
  "installMode": "lazy",
  "updateMode": "prefetch",
  "resources": {
    "files": ["/assets/**"],
    "urls": [
      "https://fonts.googleapis.com/**"
    ]
  }
}

Here we make sure that resources from Google Fonts are also cached as part of the assets named asset group. Notice how the provided URLs can be a glob pattern.


As for caching data received from API calls, instead of using assetGroups, named dataGroups will be the best option:

ngsw-config.json (partial)

...,
"dataGroups": [
  {
    "name": "from-api",
    "urls": ["/dashboard", "/settings"],
    "cacheConfig": {
      "strategy": "freshness",
      "maxSize": 15,
      "maxAge": "1h",
      "timeout": "5s"
    }
  }
],
...

With that example, data from APIs received for the dashboard or settings routes will be cached with a strategy of freshness for a maximum of 15 responses, a maximum cache age of 1 hour and a timeout of 5 second after which the result will fallback to the cache. Freshness is a network-first strategy, and alternatively we could use performance as the strategy for a cache-first strategy.

Trying it Out

With the configuration in place, we can now build the app for production (ng build --prod) and test it out using a local static server.

An easy way to run a static local server is with running http-server through npx:

$ npx http-server /dist

You can inspect the app’s Service Worker in your browser’s developer tools and you’ll also see the cached assets in the Cache storage:

Service Worker in Chrome DevTools

Now if you turn off your wifi connection, you should be able to load the app normally! 🎉✨🎊

Here are some great resources if you want to dig deeper:

⚙️ Enjoy your fresh Angular PWA! In future posts we'll explore other capabilities of the @angular/service-worker package like with push notifications and cache invalidation strategies.

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...