Home | Send Feedback

Using the Background Sync API with the Angular service worker

Published: December 09, 2018  •  pwa, javascript, ionic4

In a previous blog post, I created an example with the Background Sync API. I wrote the application with Ionic / Angular. My initial idea was to use the service worker from Angular. Because the Angular service worker currently does not have built-in support for the Background Sync API, I wrote my own code for the synchronization. But I wanted to use Angular's service worker for caching the resources, so the app also works offline.

But at that time I couldn't figure out how to add my code to the Angular service worker code. The problem was that the Angular CLI generates the code for the service worker each time you do a production build and I couldn't find a way to add a hook into this process and add my code.

So I decide to write my own service worker, not only the synchronization part but also the code that does the caching. I did this not completely from scratch, instead I used the Workbox service worker library from Google. Thanks to the flexibility of the Angular CLI it was not that complicated to integrate Workbox into the build process. See my blog post, if you are interested in this configuration. Based on this setup I wrote my synchronization example.

A few weeks ago a reader of these blog posts sent me a link to this article:
http://jakubcodes.pl/2018/06/13/enhancing-angular-ngsw/

This tutorial describes how to add custom code to Angular's generated service worker code. Exactly what I was looking for a few months ago.

In this blog post we revisit the Background Sync example from my previous post, rewrite it based on this article and use the Angular service worker instead of Workbox.

In this project we utilize the Angular service worker just for caching. The library also has support for push notification. Visit the official documentation page to learn more about the Angular service worker: https://angular.io/guide/service-worker-getting-started

Install Angular service worker

After bootstrapping the Ionic 4 app with ionic start, we can add the Angular service worker with this command.

ng add @angular/pwa --project app

Specify the correct project name. When you create a project with ionic start, the name is always app. If you are not sure what the project name is, open angular.json and look for the "projects" object. There you find the name of your project.

This command does quite a lot:


Next we copy the Ionic app from the old project. Except the file src/app/app.module.ts, nothing else needs to change. We can copy everything from the old project src/app folder into the new project. The Ionic app does not contain code that depends on the Workbox service worker.

Service Worker

Next we follow the tutorial and create two new files: src/sw-sync.js and src/sw-master.js.

Into sw-sync.js we past my custom synchronization code from the old project and remove all the Workbox specific code. You find the result here:
https://github.com/ralscha/blog/blob/master/background-sync/clientng/src/sw-sync.js

Into sw-master.js we insert two imports. They are going to import the generated service worker from Angular and our custom service worker.

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

sw-master.js

Assets

Next we have to open angular.json and add the two new files to the assets object. This tells the Angular CLI to copy the files during a build. We also have to import Dexie.js, like we did in the old project, because the synchronization service worker depends on this library.

            "assets": [
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets"
              },
              {
                "glob": "dexie.min.js",
                "input": "node_modules/dexie/dist/",
                "output": "./"
              },
              "src/manifest.json",
              "src/sw-master.js",
              "src/sw-sync.js"
            ],

angular.json

Register

Then we open the file src/app/app.module.ts and change the register call. Instead of loading the ngsw-worker.js file we want the application to load sw-master.js

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

This is already everything we have to do. Our service worker code is responsible for data synchronization and the Angular Service Worker automatically caches all the application resources. Our App should now run fine with or without a network connection to the server.

Start

Before we can start and test the app we need to start the server. There is no code change in the client app so we can use the back end from the old project. It's a Spring Boot application and you find the source code on GitHub: https://github.com/ralscha/blog/tree/master/background-sync/server

Start the server with ./mvnw spring-boot:run (.\mvnw.cmd spring-boot:run when you are on Windows) or import the project into an IDE and launch the main class (Application.java)

Next we need to do a production build with ng build --prod and then either copy the build output (www folder) to a HTTP server or start a HTTP server locally. I usually install the npm package http-server for this purpose.

npm install -D http-server

And add a script to package.json.

    "open": "http-server www -c-1 -o -a localhost -p 1234"

package.json

This way I can start the server with npm run open. This command also automatically opens the browser after it started the HTTP server.

Development

One last issue remains. Developing and testing our custom service worker code this way takes a lot of time. Each time we change something, we have to do a production build and start the web server to test it in the browser.

It would be much more convenient when we are able to develop and test our service worker code with ng serve. Angular does not create a service worker with ng serve, which is a good thing because we don't want to cache resources during development. They change all the time and it would be very burdensome to clear the cache each time we open the app in the browser.

To load our custom service worker during development we can comment out the register call in src/app/app.module.ts that we need for production and add a new register call that just loads our code. We omit the enabled option, because it is true by default.

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

We can't load sw-master.js, because it imports ngsw-worker.js which does not exist during development.

The problem with this approach is, that each time we want to do a production build we have to comment out one line and uncomment the other. This is not very convenient and when you forget it you have a broken production build.

Fortunately there is an easy solution for this. We add a new environment variable to src/environments/environment.ts and src/environments/environment.prod.ts

  serviceWorkerScript: 'sw-sync.js'

environment.ts

  serviceWorkerScript: 'sw-master.js'

environment.prod.ts

and then use that variable in the register call

    ServiceWorkerModule.register(environment.serviceWorkerScript)

app.module.ts

Depending on the build environment, the register call imports sw-sync.js (development) or sw-master.js (production).


You find the complete source code for this project on GitHub:
https://github.com/ralscha/blog/tree/master/background-sync/clientng