Using your own service worker in Angular

Since I learned to create service workers for progressive web apps, I have a specific view on what I want service workers to do and I am used to writing them myself. For Angular there's a package that brings a ready to use service worker. But how can we create and use our own service worker with Angular?

What is a progressive web app (PWA)?

A progressive web app is a web application (an application in form of a web site) that tries to give the user a feeling of a native application. Like a native application it can be installed, work offline, is responsively designed (fits to every device it is used on) and more. Service workers are part of creating this experience.

What is a service worker?

A service worker is a proxy service that is installed as part of a web app. It caches resources of the web app in the browser. When a resource (a CSS file, HTML file, image or ...) is requested, the service worker intercepts that request and has the option to serve this resource from the browsers cache. This allows for several optimizations, for instance:

  • web app resources are available even when the device is offline,
  • web app resources can be loaded quicker out of the cache and new content loaded and cached in the background and
  • the service worker can send notifications when new content is available.

These features make it very desirable to use service workers, even when not creating a full scaled PWA. A service worker is a piece of code that can be written and optimized in many different ways, depending on a projects requirement.

The Angular service worker

Angular provides its own service worker as part of the PWA schematic that can be added with ng add @angular/pwa. In terms of the service worker, the following changes will be done to the app:

  • the service worker build will be enabled in the angular.json,
  • the service-worker will be registered in the app module and
  • the configuration file for the service worker (ngsw-config.json) is created and added to the angular.json.

The service worker can be configured using the ngsw-config.json. You can define lazy fetching or prefetching for different files in terms of their first installation and their updates. You can also define special policies for requests to APIs. Finally, I want to mention that requests can be optimized towards performance, that is loading from cache whenever possible, or freshness, which means trying to load resources from the network first.

How to replace the Angular service worker

Why would you like to replace it?

Angulars service worker is already quite powerful but when it comes to special cases, the configuration might not be sufficent. It also comes in form of three additional JS-files and the configuration, which means more network traffic. On a good note, it's covering many standard use cases.

In this particular case, I wanted to update my existing hangman app which already had a service worker with an Angular based app, so I needed the service worker of the new Angular app to conform to the old service worker. Otherwise it would never update on devices that had the webapp already installed. This forced me into using my own service worker but I also prefered it for the custom caching strategy.

Replacement on app module level

So how can we replace the Angular service worker? There are two ways that I can think of. The first one, which I also used, is the replacement on app module level. What does that mean? After adding @angular/pwa to the project, the app.module.ts contains a service worker registration. It registers the /ngsw-worker.js. You can replace that filename with the name of your own service worker file like this:

ServiceWorkerModule.register('/my-service-worker.js', {
  enabled: environment.production,
  registrationStrategy: 'registerImmediately'
})

Additionally, the service worker file should be added to the assets in angular.json. It should not be added to the scripts because it is loaded by the Angular code. That's it.

Now Angular will still build its own service worker, so there will be a ngsw-worker.js, a safety-worker.js, a worker-basic.min.js and the ngsw.json in the build folder but these can be removed. Positive is, that Angular will take care of the registration of the service worker.

Replacement on app level

You can also create a PWA with Angular without using the @angular/pwa schematic at all. To achieve that, you need to have the service worker file, the manifest file and the required icon files, add them to the assets in angular.json and make the necessary adjustments in index.html. Not registering the service-worker with the registration from the @angular/pwa schematic means that it needs to be registered otherwise. This can be done inside the Angular code or with an additional script, but it is required in order to get the service worker working. Something like the following minimal example is necessary:

if (navigator.serviceWorker) {
  navigator.serviceWorker.register('/my-service-worker.js');
}

Good in this version is that there is no extra building of the Angular service worker. But it is more manual work to set everything up.

How to handle the generated files?

If you got this far, you may encounter one more problem. The files that the Angular framework generates, namely the JS and CSS files have a hash in their file names. This needs to be taken care of either in the service worker or on the file name side, if the service worker needs to reference these files.

In my case, I use a continuous deployment (CD) setup that removes the hashes from these files before installing them on the server. That way I can statically reference them for prefetching in the service worker. Note: this change also needs to be applied in the index.html file.

Alternatively, the CD pipeline could adjust the filenames inside the service worker. A third possibility is to let the service worker load the generated ngsw.json and use the filenames or even other settings from there.

Conclusion

Service workers are a powerful tool allowing for different ways to optimize resource loading by using local caching in the browser. Angular comes with its own configurable service worker implementation but it might not always be the right choice.

We looked at two ways to use a different service worker then the one offered by Angular, looking at how to include and register it. Finally, it is necessary to consider the hashes that are added to the names of the generated files if they are to be referenced by the service worker.