Enhancing Angular NGSW with custom Service Worker logic

Using Angular to build Progressive Web Apps

Angular is a great choice for PWA development. Since the 1.7.0 version of Angular CLI, service worker template is bundled into the installation package to make it easy to start with PWA development. Service Worker can be configured in Angular application using a json configuration file, providing variety of options and features. Despite it sounds great, there is one big disadvantage of using Angular Service Worker – its logic cannot be enhanced in a supported way.

Why would you need to enhance ng-sw?

In my case I needed to register a notificationclick event listener that would handle the push notification click/tap action. I must admit that Angular Service Worker does a fantastic job receiving and displaying push notifications – this part is handled by ngsw-worker.js file with absolutely no effort needed from the developer.  However, it seemed that customizing output javascript file is the only way to bring custom service worker logic I needed. This kind of workaround is simply bad – it requires to be applied each time you do some changes in ngsw configuration file. Luckily, it turned out that the issue can be easily resolved by introducing two additional files.

The solution

Let’s begin with creating two additional js files in /src folder.

sw-custom.js – a file containing a event listener


(function () {
  'use strict';
 
  self.addEventListener('notificationclick', (event) => {
    event.notification.close();
    console.log('notification details: ', event.notification);
  });
}());

sw-master.js – a file combining ngsw with our custom logic.


importScripts('./ngsw-worker.js');
importScripts('./sw-custom.js');

Register new assets

Having created the script files we need to make sure that Angular is considering them during the build. From technical point of view – they are assets, similar to favicon.ico file introduced by Angular CLI during project creation. We can register our additional assets in angular.json file (.angular-cli.json for older Angular versions):


{
 ...,
 "assets": [
 "src/favicon.ico",
 "src/assets",
 "src/manifest.json",
 "src/sw-master.js",
 "src/sw-reminders.js"
 ],
 ...
}

Re-register service worker script

The last step is to change the service worker entry point registered by Angular during application start. To do this we need to modify ServiceWorkerModule.register entry in app.module.ts in the following way:


ServiceWorkerModule.register('/sw-master.js', { enabled: environment.production })

Summary

That’s it – after production build our new script files should get copied to /dist folder and used by the application as a service worker part. If you want to see more you can also take a look into my GitHub profile, where I’ve published an example Angular app with service worker enhanced with some custom logic.

5 comments

  1. I’ve created a file named ‘sw-custom.js’ when i write my custom service worker functionalities. And I also use ngsw angular, I followed the steps but I’m not gettting the fetch event in my sw-custom.js. My custom-js file contains :

    (function () {
    ‘use strict’;

    self.addEventListener(‘fetch’, (event) => {
    console.log(‘fetch event: ‘, event);
    });
    }());

    Its not getting logged, it is not even getting passed inside fetch listener. But other functionalities such as install and active are working fine inside this ‘sw-custom.js’ file

    1. Hi, I reproduced your issue and found a possible solution. You could possibly reverse the order of script imports in sw-master.js file so your custom code registers event handler in the first place. Here’s my custom responder code which worked and didn’t cause issues with ngsw-worker.js:

      (function () {
      ‘use strict’;

      self.addEventListener(‘fetch’, function(event) {
      console.log(‘my fetch listener 1!’);
      // you should response with something!
      event.respondWith(fetch(event.request));
      });
      }());

      I must admit I’m suprised it’s working and not sure it’s 100% safe – fetch event should have only one handler as another handler might find out that the event is already handled. Anyway, I hope my solution will work for you, good luck!

  2. Excellent article, thanks!

    Just to complete your previous answer, you can indeed have 2 or more ‘fetch’ handlers registered. They will execute in the order in which you import your scripts, but if one of the handlers calls event.respondWith then the rest of the handlers will not be executed. This is the reason why it fails if you place your custom script below the Angular’s one. The former tries to catch a ‘fetch’ event but the latter will always call respondWith, either grabbing from the cache or going for the network, regardless of the dataGroup strategy.

    Thanks again for sharing

Leave a Reply

Your email address will not be published. Required fields are marked *