Home | Send Feedback | Share on Bluesky |

Using the Background Sync API with the Angular service worker

Published: 9. December 2018  •  pwa, javascript, ionic

Background Sync is a good fit for offline-first forms and task lists. The application can store changes locally while the device is offline and then send them to the server as soon as connectivity returns.

Angular's service worker already handles asset caching for production builds, but it does not implement Background Sync for application data. The clean solution is to let Angular keep its generated ngsw-worker.js for caching and load a second script with our synchronization logic.

The result is a small Ionic and Angular application that:

The Angular service worker documentation is on Angular.dev: https://angular.dev/ecosystem/service-workers

Install Angular service worker

After creating the Ionic application with ionic start, add the Angular service worker support with this command:

ng add @angular/pwa

This command does the following:


The Ionic application code itself does not need any service-worker-specific changes. The interesting part is the service worker setup.

Service Worker

Create two files in src: sw-sync.js and sw-master.js.

sw-sync.js contains the Background Sync implementation. It reads and writes todos from IndexedDB with Dexie, talks to the Spring Boot backend, and notifies open tabs when synchronization finishes.

You can find the complete synchronization code here: https://github.com/ralscha/blog/blob/master/background-sync/clientng/src/sw-sync.js

sw-master.js is the small entry point that imports Angular's generated worker and our custom sync worker.

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

sw-master.js

Assets

Open angular.json and add both service worker files and the Dexie browser bundle to the assets section. That ensures all three files are copied into the build output and are available to the service worker at runtime.

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

angular.json

Register

The application registers the service worker in src/main.ts with Angular's standalone bootstrap API.

Instead of hard-coding a single worker file, the registration reads the worker script from the environment configuration:

provideServiceWorker(environment.serviceWorkerScript)

That gives us a simple switch:

In production, sw-master.js imports Angular's generated ngsw-worker.js and our custom sync code. Angular continues to handle caching, while our script handles Background Sync.

Start

Before testing the client, start the server. The backend is a Spring Boot application, and the source code is here: 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, create a production build with ng build and serve the generated files over HTTP. With the current Angular application builder, the browser output is written to dist/app/browser.

I use the npm package local-web-server for that:

npm install -D local-web-server

Then add a script to package.json:

  "scripts": {
    "build": "ng build",
    "ng": "ng",
    "serve-dist": "ws --hostname localhost -d dist/app/browser -p 1234 -o --log.format stats",
    "start": "ng serve",
    "format": "npx prettier --write ."

package.json

After that, npm run serve-dist starts a local HTTP server and opens the browser automatically.

Development

There is one more detail to make development practical. Angular does not generate ngsw-worker.js when you run ng serve, which is exactly what we want during development because cached assets would quickly get in the way.

The environment files solve this cleanly. In development, the application registers only sw-sync.js:

export const environment = {
  production: false,
  serviceWorkerScript: 'sw-sync.js'
};

environment.ts

In production, it registers sw-master.js:

export const environment = {
  production: true,
  serviceWorkerScript: 'sw-master.js'
};

environment.prod.ts

src/main.ts then uses that value during bootstrap:

bootstrapApplication(AppComponent, {
  providers: [
    provideZoneChangeDetection(),provideIonicAngular(),
    {provide: RouteReuseStrategy, useClass: IonicRouteStrategy},
    provideRouter(routes, withHashLocation(), withPreloading(PreloadAllModules)),
    provideServiceWorker(environment.serviceWorkerScript)

main.ts

This way, ng serve can load the custom sync worker directly, while production builds load the combined worker that includes Angular's caching logic.

The client code also checks whether Background Sync is available before it registers a sync task, so the demo degrades safely in browsers that do not implement the API.


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