9
votes

I have an web application that needs to work offline. I want to create my service worker to handle the API caching/sync logic, and let the angular's service worker cache the application assets.

I got the Angular worker to register and work perfectly. But I could not get to register my worker.

I tried three methods:

//1. Does not seem to do anything. No worker built, no worker starter.
ServiceWorkerModule.register('./offline.worker', {enabled: environment.production});

//2. The worker does not get built, registering fails since the script does not exist.
navigator.serviceWorker.register('./offline.worker');

//3. The worker gets built, it even gets started, but is not registered as ServiceWorker.
new Worker('./offline.worker', {type: 'module'});

So, option 1 works fine using the docs on the angular service worker when we point to the ngsw js file. Maybe it is simply not made to work for our workers?

Option 2 is the documented/standard way to register service workers, maybe I should simply get my worker to be built? I do not know where to start to do that... The complete error I get is

Uncaught (in promise) TypeError: Failed to register a ServiceWorker for scope ('http://localhost:8080/') with script ('http://localhost:8080/offline.worker'): A bad HTTP response code (404) was received when fetching the script.

Option 3 gets everything done as I would have expected. My worker is compiled the ./offline.worker is even changed to the bundle created! That's really how I would have expected to get things going. Unfortunately the worker created like so is only a web worker, not a ServiceWorker. This means it does not get to handle FetchEvents...

For sake of completeness, here is my tsconfig.worker.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/worker",
    "lib": [
      "es2018",
      "webworker"
    ],
    "types": []
  },
  "include": [
    "src/**/*.worker.ts"
  ],
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ]
}

And the commands used to launch angular in production mode since service workers do not worker in develop:

ng build --prod
http-server dist/app
2

2 Answers

6
votes

We've added custom logic along-side the Angular Service Worker by using a simple intermediary script to load both the generated ngsw script and our own custom script. This plays well with the Angular build process.

app.module.ts

@NgModule({
  //...
  imports: [
    ServiceWorkerModule.register('sw-master.js', {
      enabled: environment.production
    }),
    //...
  ],
  //...
});

angular.json

...
"assets": [
  ...
  "src/manifest.json",
  "src/sw-custom.js",
  "src/sw-master.js"
],...

sw-master.js

importScripts('./ngsw-worker.js'); // generated by Angular build process
importScripts('./sw-custom.js');

sw-custom.js

Just an example... we were making it so users could click on push notifications and be directed to a particular app view.

(function () {
  'use strict';
  self.addEventListener('notificationclick', (event) => {
    event.notification.close();
    if (clients.openWindow && event.notification.data.url) {
      event.waitUntil(clients.openWindow(event.notification.data.url));
    }
  });
}());
0
votes

Only one service worker per scope is allowed to be registered, so you can not register more than one service worker.

You can handle both API caching/sync and application assets caching in a single service worker file.