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:
- works offline because Angular caches the app shell and static assets
- stores todo changes in IndexedDB with Dexie
- registers a Background Sync task when the browser supports it
- replays queued changes when the connection comes back
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:
- installs the
@angular/service-workerpackage - enables service worker support for production builds in
angular.json - registers the service worker in
src/main.ts - creates
src/manifest.webmanifest - adds the manifest reference to
src/index.html - creates icons in
src/assets/icons - creates
ngsw-config.jsonin the project root
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');
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"
],
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:
- development loads
sw-sync.js - production loads
sw-master.js
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 ."
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'
};
In production, it registers sw-master.js:
export const environment = {
production: true,
serviceWorkerScript: 'sw-master.js'
};
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)
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