W3cubDocs

/Angular 12

Service worker communication

Importing ServiceWorkerModule into your AppModule doesn't just register the service worker, it also provides a few services you can use to interact with the service worker and control the caching of your application.

Prerequisites

A basic understanding of the following:

SwUpdate service

The SwUpdate service gives you access to events that indicate when the service worker discovers an available update for your application or when it activates such an update—meaning it is now serving content from that update to your application.

The SwUpdate service supports four separate operations:

  • Getting notified of available updates. These are new versions of the application to be loaded if the page is refreshed.
  • Getting notified of update activation. This is when the service worker starts serving a new version of the application immediately.
  • Asking the service worker to check the server for new updates.
  • Asking the service worker to activate the latest version of the application for the current tab.

Available and activated updates

The two update events, available and activated, are Observable properties of SwUpdate:

@Injectable()
export class LogUpdateService {

  constructor(updates: SwUpdate) {
    updates.available.subscribe(event => {
      console.log('current version is', event.current);
      console.log('available version is', event.available);
    });
    updates.activated.subscribe(event => {
      console.log('old version was', event.previous);
      console.log('new version is', event.current);
    });
  }
}

Use these events to notify the user of a pending update or to refresh their pages when the code they are running is out of date.

Checking for updates

It's possible to ask the service worker to check if any updates have been deployed to the server. The service worker checks for updates during initialization and on each navigation request—that is, when the user navigates from a different address to your application. However, you might choose to manually check for updates if you have a site that changes frequently or want updates to happen on a schedule.

Do this with the checkForUpdate() method:

import { ApplicationRef, Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
import { concat, interval } from 'rxjs';
import { first } from 'rxjs/operators';

@Injectable()
export class CheckForUpdateService {

  constructor(appRef: ApplicationRef, updates: SwUpdate) {
    // Allow the app to stabilize first, before starting
    // polling for updates with `interval()`.
    const appIsStable$ = appRef.isStable.pipe(first(isStable => isStable === true));
    const everySixHours$ = interval(6 * 60 * 60 * 1000);
    const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);

    everySixHoursOnceAppIsStable$.subscribe(() => updates.checkForUpdate());
  }
}

This method returns a Promise which indicates that the update check completed successfully, though it does not indicate whether an update was discovered as a result of the check. Even if one is found, the service worker must still successfully download the changed files, which can fail. If successful, the available event indicates availability of a new version of the application.

In order to avoid negatively affecting the initial rendering of the page, ServiceWorkerModule waits for up to 30 seconds by default for the application to stabilize, before registering the ServiceWorker script. Constantly polling for updates, for example, with setInterval() or RxJS' interval(), prevents the application from stabilizing and the ServiceWorker script is not registered with the browser until the 30 seconds upper limit is reached.

Note that this is true for any kind of polling done by your application. Check the isStable documentation for more information.

Avoid that delay by waiting for the application to stabilize first, before starting to poll for updates, as shown in the preceding example. Alternatively, you might want to define a different registration strategy for the ServiceWorker.

Forcing update activation

If the current tab needs to be updated to the latest application version immediately, it can ask to do so with the activateUpdate() method:

@Injectable()
export class PromptUpdateService {

  constructor(updates: SwUpdate) {
    updates.available.subscribe(event => {
      if (promptUser(event)) {
        updates.activateUpdate().then(() => document.location.reload());
      }
    });
  }
}

Calling activateUpdate() without reloading the page could break lazy-loading in a currently running app, especially if the lazy-loaded chunks use filenames with hashes, which change every version. Therefore, it is recommended to reload the page once the promise returned by activateUpdate() is resolved.

Handling an unrecoverable state

In some cases, the version of the application used by the service worker to serve a client might be in a broken state that cannot be recovered from without a full page reload.

For example, imagine the following scenario:

  • A user opens the application for the first time and the service worker caches the latest version of the application. Assume the application's cached assets include index.html, main.<main-hash-1>.js and lazy-chunk.<lazy-hash-1>.js.
  • The user closes the application and does not open it for a while.
  • After some time, a new version of the application is deployed to the server. This newer version includes the files index.html, main.<main-hash-2>.js and lazy-chunk.<lazy-hash-2>.js (note that the hashes are different now, because the content of the files changed). The old version is no longer available on the server.
  • In the meantime, the user's browser decides to evict lazy-chunk.<lazy-hash-1>.js from its cache. Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space.
  • The user opens the application again. The service worker serves the latest version known to it at this point, namely the old version (index.html and main.<main-hash-1>.js).
  • At some later point, the application requests the lazy bundle, lazy-chunk.<lazy-hash-1>.js.
  • The service worker is unable to find the asset in the cache (remember that the browser evicted it). Nor is it able to retrieve it from the server (because the server now only has lazy-chunk.<lazy-hash-2>.js from the newer version).

In the preceding scenario, the service worker is not able to serve an asset that would normally be cached. That particular application version is broken and there is no way to fix the state of the client without reloading the page. In such cases, the service worker notifies the client by sending an UnrecoverableStateEvent event. Subscribe to SwUpdate#unrecoverable to be notified and handle these errors.

@Injectable()
export class HandleUnrecoverableStateService {
  constructor(updates: SwUpdate) {
    updates.unrecoverable.subscribe(event => {
      notifyUser(
        'An error occurred that we cannot recover from:\n' +
        event.reason +
        '\n\nPlease reload the page.'
      );
    });
  }
}

More on Angular service workers

You might also be interested in the following:

© 2010–2021 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v12.angular.io/guide/service-worker-communications