Home | Send Feedback

Using the Background Sync API with the Angular service worker

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

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 hook into this process and add my code.

So I decided to write my own service worker, not only the synchronization part but also the code that does the caching. I did this not entirely 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 the synchronization example with Workbox.

A few weeks ago, a reader sent me a link to an article that describes a way to add custom code to Angular's generated service worker code. Precisely 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 app with ionic start, we can add the Angular service worker with this command.

ng add @angular/pwa

This command does the following things:


Next, we copy the Ionic app from the old project. Except for 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 create two new files: src/sw-sync.js and src/sw-master.js.

Into sw-sync.js we paste the synchronization code. You find the complete code 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 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 because the synchronization service worker depends on this library.

              {
                "glob": "dexie.min.js",
                "input": "node_modules/dexie/dist/",
                "output": "./"
              },
              "src/manifest.webmanifest",
              "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 that 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 (dist/app folder) to an HTTP server or start an HTTP server locally. I usually install the npm package local-web-server for this purpose.

npm install -D local-web-server

And add a script to package.json.

    "serve-dist": "ws --hostname localhost -d dist/app -p 1234 -o --log.format stats",

package.json

This way, you can start the server with npm run serve-dist. This command also automatically opens the browser after it has 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 code, we have to do a production build and start the webserver to test it in the browser.

It would be much more convenient when we can 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 method call in the file 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 to 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 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