Basically, a service worker intercepts all outgoing http requests sent by your application on the browser. You can choose which request to respond to by using a set of rules (policies) to guide the service worker on how to handle different requests.

This gives your app some level of offline capabilities and can hugely impact the performance of your app by serving content even faster, on repeat calls and visits. On top of that, it will take some load off your servers, which can reduce your expenses.

NB: At its simplest, a service worker is a script that runs in the web browser and manages caching for an application.

Introduction

In this post, I will take a closer look at how to optimize the performance of angular app using a Service Worker. We will be using an old tutorial we worked on sometime back for creating an Angular Material AutoComplete with HTTP lookup. It queries the Github API with the keyword you are typing and return a list of possible repos for you to choose one. The more you type, the more relevant the queries become.

You can learn more about that here. So, in this post, we will be adding and configuring a service worker to the demo we created. We are going to aggressively cache the queries to the Github API using a service worker for 30mins. In case of repeated requests, the service worker will serve the same results from a local cache. Without further ado:

Adding Support for a Service Worker

Adding a Service Worker is rather s straight forward process in Angular 6. This is due to angular schematics support, which was introduced in Angular 6. We can achieve this by using the command below:

$ ng add @angular/pwa

The above command, makes modification to your app, adding some files and boilerplate code for both Progressive Web Apps (PWAs) and Service Worker.

Let’s take a close look at Service Worker specific modifications: In the main app module, the following code was added:

ServiceWorkerModule.register('ngsw-worker.js', {
  enabled: environment.production
});

This enables a service worker for your application during production. Please note, due to security issues, a service worker is only enabled when your site is loaded on HTTPs or on Localhost. And another file was also created at the root of the workspace – ngsw-config.json.

By default, it contains rules to cache static assets of a our PWA. They are all contained in a json property called assetsGroup, which are resources that are part of the app versions and are all updated together. Resources such as JS, CSS, Images and any links from CDNs and other static resources should be added here.

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

Configuring Service Worker for API Calls

By default, http requests are not cached by the service worker. You must add rules to specifically cache various HTTP requests. This are grouped together in a Data Group property.

NB: Data Groups is an array of Data Group items, which can be presented in the form of the following Typescript Interface:

export interface DataGroup {
  name: string;
  urls: string[];
  version?: number;
  cacheConfig: {
    maxSize: number;
    maxAge: string;
    timeout?: string;
    strategy?: 'freshness' | 'performance';
  };
}

Taking a close look at the above interface, it has the following properties:

  • Name – The name of the policy. Can be anything.

  • URLS – An array of URLs strings to cache. Could follow a pattern.

    Note: Negative glob patterns are not supported.

  • Version (Optional) – Indicates that a new version of the API is backward compatible and old versions may be discarded.

  • CacheConfig: This has 4 properties:

    • MaxSizeThe maximum number of items to be cached.
    • MaxAge – How long something stays in the cache. Measured in minutes (m), hours (h), days (d), milliseconds (u) and seconds (s).
    • Timeout – Indicates how long the service worker will wait for a response before falling back to a service worker cache.
    • Strategy – Two strategy can be applied here:
      • Freshness – always looks for fresh content from the backend, fallbacks to cache in case of timeout – relies on timeout setting above.
      • Perfomance – always use a local cache whenever available and has not expired – relies on maxAge setting above.

Configuring our Demo App Service Worker

First, we are going to add a data group to our ngsw-config.json file.

{
  //...
  "assetGroups": [
    // ...
  ],
  "dataGroups": [
    //...
  ]
}

Next, we are going to add a new caching policy or rule for our API Call to Github. We will name the policy Github API Call. The new policy should look like this:

{
  "name": "Github API Call",
  "urls": ["https://api.github.com/search/repositories"],
  "cacheConfig": {
    "maxAge": "30m",
    "strategy": "performance",
    "maxSize": "10"
  }
}

In the above configuration, we are setting a maximum age of 30 minutes for all request to our URLs. You can reduce that time so that it reflects the frequency of updates on your backend. I am also using the performance strategy, requiring use of local cache if it is not expired. I have set the maximum number of cached requests to 10 requests, after that, old request are discarded.

Service Worker – Optimizing the Performance of an Angular App

Demo and Source Code

If you want to see the above demo in action, you can find it here. Be sure to open the dev tools and observe the network traffic as the service worker responds to the requests. Run a few requests first, then take the app offline and repeat the request and see whether it still works. You can also find the source code of the angular app here.