skip to content

Service Worker Package

@angular/service-worker builds a caching worker from ngsw-config.json, and SwUpdate tells the app when a new version is ready. Interviewers probe prefetch vs lazy asset groups and stale clients.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

6

How should an Angular app use SwUpdate's versionUpdates, checkForUpdate() and activateUpdate() to move users to a new release, and why is reloading preferred?

level: seniorimportance: must knowfreq 36%

answer

  1. filter for VERSION_READY
  2. prompt, then reload
  3. check on a schedule once stable
  4. shell and chunk mismatch
  5. unrecoverable needs a reload too

basics

~10 s

Subscribe to versionUpdates, react to VERSION_READY by prompting the user and reloading, call checkForUpdate() for long-lived tabs, and avoid activateUpdate() without a reload because the running shell can mismatch new lazy chunks.

solid answer

~40 s

`SwUpdate` from `@angular/service-worker` is the app-side API. `versionUpdates` emits `VERSION_DETECTED`, `VERSION_READY`, `VERSION_INSTALLATION_FAILED` and `NO_NEW_VERSION_DETECTED`; the app filters for `VERSION_READY`, which carries `currentVersion` and `latestVersion`, and prompts the user to reload. `checkForUpdate()` asks the worker to check now and resolves `true` when a new version is ready, useful for tabs that stay open for days; start polling only after the app is stable so registration is not delayed. `activateUpdate()` switches the current tab to the latest version without reloading, but the documentation warns this can break the app: the running shell may request lazy chunks whose filenames changed. Also subscribe to `unrecoverable` and reload when the served version cannot recover. When the worker is disabled, `isEnabled` is `false`.

code

ts · 22 lines
ts
import { Injectable, inject } from '@angular/core';
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
import { filter } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class UpdatePrompt {
  private readonly updates = inject(SwUpdate);

  start(): void {
    if (!this.updates.isEnabled) return;

    this.updates.versionUpdates
      .pipe(filter((e): e is VersionReadyEvent => e.type === 'VERSION_READY'))
      .subscribe(() => {
        if (confirm('A new version is available. Reload now?')) {
          document.location.reload();
        }
      });

    this.updates.unrecoverable.subscribe(() => document.location.reload());
  }
}

go deeper

for a junior

Recall that SwUpdate.versionUpdates tells the app a new version is ready and that the usual response is to reload.

for a middle

Explain the four version event types, checkForUpdate's promise, and why activateUpdate without a reload is risky.

for a senior

Design the update experience: when to prompt, polling for long-lived tabs after stability, and handling unrecoverable state.

for a principal

Set the release policy for how quickly clients must update, including forced reloads for critical fixes carried in appData.

## What SwUpdate is for The Angular service worker downloads new versions in the background and serves them on the next load. `SwUpdate`, injected from `@angular/service-worker`, lets the running app **observe and influence** that process. It is available once `provideServiceWorker()` is in the application providers. ## versionUpdates: knowing a version is ready `versionUpdates` is an `Observable` of four event types: | Event type | Meaning | |---|---| | `VERSION_DETECTED` | A new version was found on the server and is about to download | | `VERSION_READY` | The new version is downloaded and ready; carries `currentVersion` and `latestVersion` (each with `hash` and optional `appData`) | | `VERSION_INSTALLATION_FAILED` | Downloading or installing the new version failed; carries an `error` string | | `NO_NEW_VERSION_DETECTED` | A check found nothing new | `VERSION_READY` is the one that drives user-facing behaviour. `appData` comes from `ngsw-config.json` and can carry release notes or a "critical" flag for the prompt. ## The recommended flow: prompt and reload 1. Filter `versionUpdates` for `VERSION_READY`. 2. Tell the user a new version is available, ideally at a moment that does not interrupt work (not mid-form). 3. On confirmation, call `document.location.reload()`. Reloading gives the tab a **consistent** new version: new `index.html`, new bundles, new lazy chunks together. ## activateUpdate(): why it is risky `activateUpdate()` updates the current tab to the latest ready version **without reloading**. The already-running JavaScript is still the old build, but subsequent requests are served from the new version. If the old code then loads a lazy route, it asks for a chunk filename from the old build that the new version may not contain, and the app breaks. The API documentation says to use it only when you are certain it is safe for your case. It resolves `true` if an update was activated and `false` if none was available. ## checkForUpdate(): for long-lived tabs The worker checks for updates during initialization and on navigation requests. A tab left open for days, such as a kiosk or an operations dashboard, may never trigger either. `checkForUpdate()` asks the worker to check now: - resolves `true` when a new version was found and is ready to activate; - resolves `false` when there is nothing new; - rejects on error, or immediately when the service worker is not enabled. Start periodic checks only **after the application is stable**. By default the worker is registered when the app stabilizes, with a 30-second cap (`registerWhenStable:30000`). In a zone-based app, a timer started at startup keeps it from stabilizing, so registration waits for the cap. The documented pattern waits for `ApplicationRef.isStable` to emit `true`, then starts an interval. ## unrecoverable: when reloading is the only fix `unrecoverable` emits when the version serving this tab is broken beyond repair, for example when the browser evicted a lazy chunk of an old version that the server no longer has. The event carries a `reason`. The only fix is a full reload, so the handler should tell the user and reload. ## When the worker is not active If the service worker is disabled (for example `enabled: !isDevMode()` in development) or unsupported, `SwUpdate.isEnabled` is `false`, `versionUpdates` and `unrecoverable` never emit, and the methods reject. Code that injects `SwUpdate` should check `isEnabled` before calling methods, so development builds do not log errors. ## Common mistakes - **Reloading on `VERSION_DETECTED`**: the new version is not downloaded yet, so the reload is served the old version again. - **Reloading without asking** in the middle of user input, losing unsaved work. - **Polling from app startup** in a zone-based app, delaying worker registration until the 30-second cap. - **Treating `activateUpdate()` as a soft reload**: it changes which version answers future requests, not the code already running. - **Calling `SwUpdate` methods in development** without checking `isEnabled`, producing rejected promises in the console. ## Putting it together A typical app has one root-provided service that: - subscribes to `versionUpdates` and prompts on `VERSION_READY`; - subscribes to `unrecoverable` and forces a reload; - in long-lived contexts, polls `checkForUpdate()` after stability; - never calls `activateUpdate()` unless the app has no lazy code or has proven it safe.

  • What exactly can break after activateUpdate() without a reload?
    The code already running in the tab is the old build. After activation the worker serves the new version's files, so when old code lazy-loads a route it may request a chunk filename that only existed in the old build. That request fails and the navigation breaks, which is why the docs recommend reloading.
  • Why can calling checkForUpdate() on an interval from startup delay service worker registration?
    The default strategy registers when the app becomes stable, capped at 30 seconds. In a zone-based app a recurring timer keeps it from stabilizing, so registration waits for the cap. Waiting for `ApplicationRef.isStable` before starting the interval avoids the delay.

saying these in an interview costs you the question

  • activateUpdate() is the safest way to update a running tab
  • VERSION_DETECTED means the new version is ready to use
  • checkForUpdate() also reloads the page when it finds a version
  • SwUpdate methods work the same when the worker is disabled
  • unrecoverable can be fixed by calling checkForUpdate()
open as a page

After a new deploy of an Angular app using @angular/service-worker, why do returning users see the old version first?

level: juniorimportance: should knowfreq 38%

basics

~20 s

The Angular service worker serves the version it has already cached without waiting for the network, then checks ngsw.json, downloads a changed version in the background, and serves it on the next load or reload.

open as a page

In an Angular ngsw-config.json assetGroup, what do installMode and updateMode set to prefetch or lazy change, and which suits app bundles versus images?

level: middleimportance: should knowfreq 32%

basics

~10 s

installMode decides whether a group's files are cached up front (prefetch) or only when requested (lazy); updateMode decides whether changed cached files are re-downloaded immediately on a new version or when next requested.

open as a page

In Angular's ngsw-config.json, how do the dataGroups strategies performance and freshness answer an API request, and what do maxAge and timeout control?

level: middleimportance: should knowfreq 30%

basics

~10 s

performance (the default) answers from cache while the entry is younger than maxAge; freshness goes to the network and falls back to the cache only when the request fails or exceeds timeout.

open as a page

An Angular field-inspection app must start and show assigned inspections with no signal; how would you configure @angular/service-worker, and what can it not do for offline submissions?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Prefetch all app code, cache inspections in a freshness dataGroup with a short timeout and long maxAge, cache reference data with performance, and make sure the worker installs before going offline; submissions still need an app-level queue.

open as a page

When a broken build of an Angular app is stuck in users' browsers through @angular/service-worker, how do the ngsw.json fail-safe and safety-worker.js remove it?

level: seniorimportance: nice to knowfreq 20%

basics

~10 s

If ngsw.json returns 404, the Angular worker deletes its caches and unregisters itself; safety-worker.js, served at the old worker script URL, unregisters any worker and removes caches for clients that cannot otherwise be reached.

open as a page