| |

Angular 94 🅰️ Service Workers and Offline Support

⏱️ Estimated read: 14 min | |

The previous chapter introduced Progressive Web Apps and the service worker that makes them possible. This chapter goes deeper into the service worker itself: what it is, how it intercepts requests, how Angular’s implementation differs from a hand-written one, and how to debug it when the cache is serving stale content or the update mechanism is not working as expected.

The Angular service worker is a network proxy. It runs in the browser, separate from the application, and intercepts every outgoing HTTP request. It can serve a cached response, forward the request to the network, or return a synthetic response. It persists after the user closes the tab. The next time the browser loads the application, the service worker loads first and can satisfy the entire application load from its cache .

Key point: The Angular service worker is not a generic caching library. It is a versioned caching system. The application is cached as one unit, and all files update together. A running application continues to use the same version of all files. It does not suddenly start receiving cached files from a newer version. When the user refreshes, they see the latest fully cached version. This guarantee is stronger than the normal web deployment model, where lazily loaded code can come from a different version than the initial code .


Why the service worker matters

A service worker is not just a cache. It changes the relationship between the application and the network.

The offline problem. A user opens the application on a train with no connectivity. A traditional web application shows an error page. A PWA loads from the service worker’s cache and works as if the network were present. The Angular service worker caches the application shell, the JavaScript bundles, the CSS, and the assets. When the user refreshes, the application loads from the cache .

The performance problem. Even on a fast network, round-trip delays add latency. A service worker that serves from the cache eliminates those delays. The application loads faster because the network is not in the critical path .

The consistency problem. Without a service worker, a running application can receive lazily loaded code from a newer version than its initial code. This can cause version mismatches and broken behavior. The Angular service worker guarantees that a running application continues to use the same version of all files until the user reloads .

The update problem. A new version of the application is deployed. The user should see the new version, but not in the middle of a task. The Angular service worker downloads the new version in the background and notifies the application when it is ready. The application can prompt the user to reload, or it can activate the update automatically .

The trade-off. The service worker adds complexity. Caching is aggressive. A stale cache can serve old content. The update mechanism must be understood and configured. The Angular service worker is designed to minimize these risks, but the developer must still test offline behavior and update flows thoroughly .


a. How the Angular Service Worker Works

The Angular service worker is a script that runs in the browser and manages caching for the application. It functions as a network proxy, intercepting all outgoing HTTP requests and choosing how to respond .

The service worker loads a manifest file from the server. The file is called ngsw.json (not to be confused with the web app manifest manifest.webmanifest). It describes the resources to cache and includes hashes of every file’s contents. When an update to the application is deployed, the contents of the manifest change, informing the service worker that a new version of the application should be downloaded and cached. The manifest is generated from a CLI-generated configuration file called ngsw-config.json .

The ngsw-config.json file defines two types of caching groups: asset groups and data groups .

Asset groups define the application resources that are cached. The app group caches the application shell — index.html, favicon.ico, the JavaScript and CSS bundles, and the web manifest. The assets group caches files under the assets directory and images and fonts in the output path . The installMode determines when the resources are cached. prefetch downloads all specified resources during service worker installation. lazy caches resources only when they are first requested .

Data groups define the runtime caching for API responses and other dynamic data. Each group specifies a URL pattern, a caching strategy (performance, freshness, or optimize), and a maximum size and age for the cache. The performance strategy serves from the cache first and updates in the background. The freshness strategy tries the network first and falls back to the cache .

The Angular service worker checks for updates during initialization and on each navigation request. It also supports manual checks through the SwUpdate service .


b. The Service Worker Lifecycle

A service worker has its own lifecycle, separate from the application’s lifecycle. Understanding the lifecycle explains when the cache is populated, when updates are detected, and when the new version takes effect.

Install. The service worker is installed when the application is first loaded, or when a new version is detected. During installation, the prefetch asset groups are downloaded and cached. If the installation fails, the service worker is not activated, and the old version continues to serve .

Activate. The service worker is activated after installation. If there is an older service worker, it is replaced. The activation step is where the old caches are cleaned up .

Fetch. Once activated, the service worker intercepts every request. It checks the asset groups and data groups in the order they appear in ngsw-config.json. The first matching group handles the request .

Update. When a new version of the application is deployed, the ngsw.json manifest changes. The service worker detects the change, downloads the new assets in the background, and emits a VERSION_READY event. The running application continues to use the old version until the user reloads or the application activates the update .

The registration strategy determines when the service worker is registered. The default is registerWhenStable:30000, which waits for the application to stabilize before registering, up to 30 seconds. This avoids negatively affecting the initial rendering of the page. Constant polling for updates prevents the application from stabilizing, so the recommended pattern is to wait for isStable before starting the polling interval .


c. Debugging the Service Worker

The Angular service worker exposes debugging information under the ngsw/ virtual directory. The single exposed URL is ngsw/state . Navigating to http://localhost:8080/ngsw/state shows the current state of the service worker.

The debug page includes several fields. The Driver state shows whether the service worker is operating normally (NORMAL), in a degraded state where only existing clients are served from cache (EXISTING_CLIENTS_ONLY), or in a safe mode where all traffic goes to the network (SAFE_MODE). The Latest manifest hash is the hash of the current application version. The Last update check shows when the service worker last checked for updates. The Version section lists the cached versions and the clients using them. The Idle task queue shows pending background tasks. The Debug log records internal errors .

The browser’s developer tools also provide service worker debugging. In Chrome, the Application tab shows the service worker status, the cached resources, and the manifest information. The Network tab shows requests served by the service worker with the (ServiceWorker) label in the Size column .

To bypass the service worker, set ngsw-bypass as a request header or query parameter. The value is ignored. This is useful when you need the browser to handle the request directly, such as when a feature is not supported by the service worker .


Complete Example Session

This session demonstrates the service worker in action: initial load, offline behavior, updates, and debugging.

# ============================================
# PART 1: BUILD AND SERVE THE APPLICATION
# ============================================

ng build

# Serve the production build with a local server
npx http-server -p 8080 -c-1 dist/my-app/browser

# The -c-1 flag disables caching, so the server always returns fresh files.
# The service worker is registered on first load.

# ============================================
# PART 2: INITIAL LOAD
# ============================================

# Open http://localhost:8080 in a browser.
# The application loads normally.
# The service worker is registered.
# The prefetched assets are cached.

# ============================================
# PART 3: SIMULATE OFFLINE
# ============================================

# In Chrome DevTools:
# 1. Open the Network tab.
# 2. Select "Offline" in the Throttling dropdown.
# 3. Refresh the page.

# The application loads normally.
# In the Network tab, the Size column shows (ServiceWorker).
# This means the resources are served from the cache.

# ============================================
# PART 4: CHECK THE DEBUG PAGE
# ============================================

# Navigate to http://localhost:8080/ngsw/state

# Output:
# NGSW Debug Info:
# Driver version: 19.2.0
# Driver state: NORMAL ((nominal))
# Latest manifest hash: abc123...
# Last update check: never
# === Version abc123... ===
# Clients: 7b79a015-...
# === Idle Task Queue ===
# Last update tick: 1s496u
# Last update run: never
# Task queue:
# - init post-load (update, cleanup)
# Debug log:

# The driver state is NORMAL.
# The manifest hash identifies the current version.

# ============================================
# PART 5: MAKE A CHANGE AND REBUILD
# ============================================

# Edit src/app/app.component.html
# Change the title.

ng build

# The build generates a new ngsw.json with a new hash.

# ============================================
# PART 6: RELOAD THE APPLICATION
# ============================================

# Refresh http://localhost:8080

# The service worker detects the new version.
# It downloads the new assets in the background.
# The application continues to serve the old version.

# After a moment, the service worker emits VERSION_READY.
# The application can prompt the user to reload.

# ============================================
# PART 7: THE SWUPDATE SERVICE
# ============================================

# update.service.ts
import { Injectable, inject } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';

@Injectable({ providedIn: 'root' })
export class UpdateService {
  private swUpdate = inject(SwUpdate);

  constructor() {
    if (this.swUpdate.isEnabled) {
      this.swUpdate.versionUpdates.subscribe((event) => {
        switch (event.type) {
          case 'VERSION_DETECTED':
            console.log('Downloading new version...');
            break;
          case 'VERSION_READY':
            if (confirm('New version available. Load it?')) {
              window.location.reload();
            }
            break;
          case 'VERSION_INSTALLATION_FAILED':
            console.error('Update failed:', event.error);
            break;
        }
      });
    }
  }
}

# ============================================
# PART 8: CHECKING FOR UPDATES MANUALLY
# ============================================

# check-for-update.service.ts
import { ApplicationRef, inject, Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
import { concat, interval } from 'rxjs';
import { first } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class CheckForUpdateService {
  private appRef = inject(ApplicationRef);
  private swUpdate = inject(SwUpdate);

  constructor() {
    const appIsStable$ = this.appRef.isStable.pipe(
      first((isStable) => isStable === true)
    );
    const everySixHours$ = interval(6 * 60 * 60 * 1000);
    const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);

    everySixHoursOnceAppIsStable$.subscribe(async () => {
      try {
        const updateFound = await this.swUpdate.checkForUpdate();
        console.log(updateFound ? 'Update available.' : 'Up to date.');
      } catch (err) {
        console.error('Failed to check for updates:', err);
      }
    });
  }
}

# ============================================
# PART 9: BYPASSING THE SERVICE WORKER
# ============================================

# Add the ngsw-bypass header or query parameter to bypass the service worker.
# Example with curl:
curl -H "ngsw-bypass: true" http://localhost:8080

# Or in a URL:
# http://localhost:8080/?ngsw-bypass=true

# The service worker ignores the value.
# The request is handled by the network.

# ============================================
# PART 10: THE SERVICE WORKER CHECKLIST
# ============================================

# 1. Run ng add @angular/pwa
# 2. Configure ngsw-config.json for asset and data groups
# 3. Build with ng build
# 4. Serve over HTTPS or localhost
# 5. Test offline behavior with the Network tab
# 6. Check /ngsw/state for debugging
# 7. Handle updates with SwUpdate
# 8. Use ngsw-bypass when needed
# 9. Test in an incognito window to avoid stale cache
# 10. Monitor the Debug log for errors

The ten parts cover building and serving the application, the initial load, simulating offline, checking the debug page, making a change and rebuilding, reloading the application, the SwUpdate service, checking for updates manually, bypassing the service worker, and the service worker checklist.


Quick Reference

The Service Worker Configuration

GroupPurposeExample
assetGroupsApplication resourcesapp, assets
dataGroupsAPI responsesapi-performance
installModeWhen to cacheprefetch, lazy
updateModeWhen to updateprefetch, lazy
strategyCache behaviorperformance, freshness

The SwUpdate Events

EventWhen Emitted
VERSION_DETECTEDNew version found on server
VERSION_READYNew version downloaded and ready
VERSION_INSTALLATION_FAILEDInstallation failed
NO_NEW_VERSION_DETECTEDChecked, no new version
VERSION_FAILEDCritical failure affecting all clients

The Debug Fields

FieldMeaning
Driver stateNORMAL, EXISTING_CLIENTS_ONLY, SAFE_MODE
Latest manifest hashCurrent application version
Last update checkWhen the service worker last checked
VersionCached versions and clients
Idle task queuePending background tasks
Debug logInternal errors

The Registration Strategies

StrategyBehavior
registerWhenStable:30000Wait for app to stabilize, max 30s
registerImmediatelyRegister immediately
registerWithDelay:5000Register after 5 seconds

The Bypass Mechanisms

MechanismExample
Headerngsw-bypass: true
Query param?ngsw-bypass=true

Best Practices

✅ Do This:

# Use the CLI schematic to add PWA support
ng add @angular/pwa                                             # ✅
// Check if the service worker is enabled
if (this.swUpdate.isEnabled) { }                                // ✅
// Prompt the user before reloading
if (confirm('New version available. Load it?')) {               // ✅
// Wait for app stability before polling
concat(appIsStable$, everySixHours$)                            // ✅
# Check the debug page when troubleshooting
# http://localhost:8080/ngsw/state                              // ✅

❌ Don’t Do This:

// Don't activate updates without reloading
this.swUpdate.activateUpdate();  // may break the app             // ❌
// Don't poll for updates before the app stabilizes
interval(60000).subscribe(() => this.swUpdate.checkForUpdate()); // ❌
# Don't test service workers with ng serve
ng serve  // service worker not active                           // ❌
// Don't skip HTTPS in production
// Service workers only register over HTTPS or localhost.        // ❌

Common Pitfalls

PitfallWhy It HappensFix
Service worker not registeringNot served over HTTPSUse HTTPS or localhost
Old content servedService worker cacheRefresh or use incognito
Update not detectedNo polling or navigationUse checkForUpdate()
Debug page shows SAFE_MODEHash mismatch or cache failureCheck the Debug log
Stale API dataAggressive cachingConfigure dataGroups carefully
App broken after updateVersion mismatchReload the page

Real-World Examples

1. Add PWA Support

ng add @angular/pwa

2. Check for Updates

const updateFound = await this.swUpdate.checkForUpdate();

3. Handle Version Ready

this.swUpdate.versionUpdates.subscribe((event) => {
  if (event.type === 'VERSION_READY') { /* prompt */ }
});

4. Reload on Update

window.location.reload();

5. Debug Page

# Navigate to /ngsw/state

6. Bypass Service Worker

curl -H "ngsw-bypass: true" http://localhost:8080

7. Configure Caching

{ "assetGroups": [{ "name": "app", "installMode": "prefetch" }] }

8. Cache API Responses

{ "dataGroups": [{ "name": "api", "urls": ["/api/**"] }] }

9. Test Offline

# DevTools > Network > Offline > Refresh

10. Registration Strategy

provideServiceWorker('ngsw-worker.js', { registrationStrategy: 'registerWhenStable:30000' })

Visual

The Service Worker as Network Proxy

┌──────────────────────────────────────────────┐
│  SERVICE WORKER AS NETWORK PROXY             │
│                                              │
│  Application                                 │
│    └─ HTTP request                           │
│       │                                      │
│       ▼                                      │
│  Service Worker                              │
│    ├─ Check cache                            │
│    │    ├─ Cache hit → return cached         │
│    │    └─ Cache miss → forward to network   │
│    └─ Update cache in background             │
│                                              │
│  The service worker intercepts every request.│
│  It decides how to respond.                  │
│                                              │
└──────────────────────────────────────────────┘

The Update Flow

┌──────────────────────────────────────────────┐
│  UPDATE FLOW                                 │
│                                              │
│  New version deployed                        │
│       │                                      │
│       ▼                                      │
│  Service worker detects new ngsw.json        │
│       │                                      │
│       ▼                                      │
│  VERSION_DETECTED                            │
│       │                                      │
│       ▼                                      │
│  Download new assets in background           │
│       │                                      │
│       ▼                                      │
│  VERSION_READY                               │
│       │                                      │
│       ├─ Prompt user to reload               │
│       └─ Reload activates new version        │
│                                              │
│  The old version continues to serve until    │
│  the user reloads.                           │
│                                              │
└──────────────────────────────────────────────┘

The Debug Page Fields

┌──────────────────────────────────────────────┐
│  ngsw/state                                  │
│                                              │
│  Driver state:                               │
│    NORMAL → working correctly                │
│    EXISTING_CLIENTS_ONLY → degraded          │
│    SAFE_MODE → all traffic to network        │
│                                              │
│  Latest manifest hash:                       │
│    The current application version.          │
│                                              │
│  Last update check:                          │
│    When the service worker last checked.     │
│                                              │
│  Idle task queue:                            │
│    Pending background tasks.                 │
│                                              │
│  Debug log:                                  │
│    Internal errors.                          │
│                                              │
└──────────────────────────────────────────────┘

The Caching Groups

┌──────────────────────────────────────────────┐
│  ngsw-config.json                            │
│                                              │
│  assetGroups:                                │
│    app:                                      │
│      ├─ index.html                           │
│      ├─ *.css, *.js                          │
│      └─ prefetched on install                │
│                                              │
│    assets:                                   │
│      ├─ /assets/**                           │
│      ├─ images, fonts                        │
│      └─ lazy loaded, prefetched on update    │
│                                              │
│  dataGroups:                                 │
│    api-performance:                          │
│      ├─ /api/**                              │
│      ├─ cache first                          │
│      └─ max 100 entries, 1 day               │
│                                              │
└──────────────────────────────────────────────┘

Summary

ItemValue
Service worker definitionNetwork proxy that intercepts HTTP requests
Angular service workerVersioned caching utility
Configurationngsw-config.json
Generated manifestngsw.json
Asset groupsApplication resources (app, assets)
Data groupsAPI responses
Install modesprefetch, lazy
Cache strategiesperformance, freshness, optimize
Update serviceSwUpdate
Debug page/ngsw/state
Bypassngsw-bypass header or query param
HTTPS requirementRequired except on localhost

Key takeaways:

  • The Angular service worker is a network proxy that intercepts every HTTP request. It can serve a cached response, forward the request to the network, or return a synthetic response. It persists after the tab closes and loads first on the next visit .
  • The service worker is a versioned caching system. The application is cached as one unit. A running application continues to use the same version of all files. When the user refreshes, they see the latest fully cached version .
  • The ngsw-config.json file defines the caching behavior. Asset groups cache application resources. Data groups cache API responses. The installMode determines when resources are cached. The strategy determines how requests are handled .
  • The SwUpdate service provides access to update events. The versionUpdates observable emits events when a new version is detected, ready, or fails. The application can prompt the user to reload or check for updates manually .
  • The debug page at /ngsw/state shows the service worker’s state. It includes the driver state, the latest manifest hash, the last update check, the cached versions, the idle task queue, and the debug log .
  • The service worker can be bypassed with the ngsw-bypass header or query parameter. The value is ignored. This is useful when a feature is not supported by the service worker .
  • Constant polling prevents the application from stabilizing. The service worker waits up to 30 seconds for the application to stabilize before registering. The recommended pattern is to wait for isStable before starting the polling interval .

Remember: The Angular service worker is a versioned caching system that runs in the browser and intercepts every HTTP request. It caches the application as one unit, serves it offline, and updates it in the background. The ngsw-config.json file defines the caching behavior. The SwUpdate service handles the update flow. The /ngsw/state debug page shows the current state. The service worker works over HTTPS, it is installable, and it is resilient to network failures. The trade-off is complexity: the cache is aggressive, the update mechanism must be understood, and the debug page is the tool for troubleshooting.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!