Skip to content
Angular ng pwa 4 min read

Adding a Service Worker

A service worker is the engine that powers an installable, offline-capable Angular app. Rather than wiring up the low-level Service Worker API by hand, Angular ships @angular/service-worker together with a schematic that configures everything for you in a single command. This page walks through running ng add @angular/pwa, understanding the generated ngsw-config.json, and how the worker gets registered and served.

Installing with ng add

The fastest, least error-prone way to add a service worker is the official PWA schematic. From the root of an existing Angular project, run:

ng add @angular/pwa

The schematic performs several edits at once: it installs @angular/service-worker, generates an ngsw-config.json at the project root, adds the manifest and icon assets, registers the worker in your application config, and flips on "serviceWorker": true in angular.json.

Output:

Installing packages for tooling via npm.
Installed packages for tooling via npm.
CREATE ngsw-config.json (664 bytes)
CREATE public/manifest.webmanifest (1366 bytes)
CREATE public/icons/icon-512x512.png (...)
UPDATE angular.json (...)
UPDATE src/index.html (...)
UPDATE src/app/app.config.ts (...)

The service worker only runs in production-style builds. ng serve deliberately does not register it, so you must build and serve the output (covered below) to see it in action.

Registering the worker

For a standalone application, the schematic adds provideServiceWorker to your app.config.ts. It registers ngsw-worker.js and waits until the app is stable (or 30 seconds elapse) before activating, so registration never competes with your initial render.

// src/app/app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideServiceWorker } from '@angular/service-worker';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerWhenStable:30000',
    }),
  ],
};

The registrationStrategy controls when registration happens. The available strategies are:

StrategyBehaviour
registerWhenStable:<ms>Wait for app stability, falling back to the timeout in ms
registerImmediatelyRegister as soon as the app bootstraps
registerWithDelay:<ms>Register after a fixed delay
A custom factoryA function returning an Observable that fires when you want

Understanding ngsw-config.json

ngsw-config.json is the source of truth for what the service worker caches and how. It is read at build time and compiled into ngsw.json (the runtime manifest). A freshly generated file looks like this:

// ngsw-config.json
{
  "$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/manifest.webmanifest",
          "/*.css",
          "/*.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/media/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

A few keys are worth knowing up front:

  • index — the fallback document served for navigation requests, which is what makes deep links work offline in a single-page app.
  • assetGroups — bundles of static files baked into the build. The app group uses prefetch so the application shell is cached on install; the assets group uses lazy so large media is only cached on first request.
  • installModeprefetch caches everything immediately at install; lazy caches a resource the first time it is requested.
  • updateMode — how cached resources refresh when a new version ships; prefetch re-downloads in the background, lazy waits for the next request.

You can also add a dataGroups array to cache API responses, and a navigationUrls array to control which navigation requests fall back to index.html. Those are covered in the caching page linked below.

Building and serving

Because the worker is disabled in dev mode, you need a real build plus a static server to exercise it.

ng build
npx http-server -p 8080 -c-1 dist/my-app/browser

Open http://localhost:8080, then check the browser DevTools under Application → Service Workers. You should see ngsw-worker.js activated and running.

Output:

Starting up http-server, serving dist/my-app/browser
Available on:
  http://127.0.0.1:8080
Hit CTRL-C to stop the server

To confirm offline support, tick Offline in the DevTools Network panel and reload — the app shell still loads from the cache rather than the network.

Best practices

  • Always build with ng build (production configuration) and serve the static output; never expect the worker to run under ng serve.
  • Keep prefetch for the app shell so the first offline visit works, and reserve lazy for heavy or optional media to avoid bloating the install.
  • Serve over HTTPS in production — service workers are blocked on insecure origins, with localhost as the only exception.
  • Disable HTTP caching of ngsw.json and ngsw-worker.js on your server so clients reliably pick up new versions.
  • Edit ngsw-config.json rather than hand-writing worker logic; let Angular compile the runtime manifest for you.
  • Use provideServiceWorker with enabled: !isDevMode() so the worker is environment-aware without manual flags.
Last updated June 14, 2026
Was this helpful?