Skip to main content

Angular Service Workers

Introduction

Service Workers are a key technology for creating Progressive Web Applications (PWAs). They act as a proxy between your web application, the browser, and the network, enabling powerful features like offline functionality, background sync, and push notifications.

In this tutorial, we'll explore how Angular provides built-in support for service workers, making it easier to transform your Angular application into a PWA with enhanced user experiences regardless of network conditions.

What are Service Workers?

Service Workers are JavaScript files that run separately from the main browser thread, intercepting network requests, caching resources, and enabling offline experiences. They operate as a programmable network proxy, giving you complete control over how your app responds to network requests.

Key capabilities of service workers include:

  • Offline functionality: Allow your app to work without an internet connection
  • Performance improvement: Serve cached resources for faster load times
  • Background processing: Perform tasks even when the app isn't open
  • Push notifications: Enable your app to receive push notifications

Setting Up Service Workers in Angular

Prerequisites

Before adding service workers to your Angular application, ensure you have:

  • Angular CLI installed (version 6.0 or higher)
  • An existing Angular project

Step 1: Add PWA Support to Your Angular Project

Angular provides a schematic to add service worker capabilities to your project:

bash
ng add @angular/pwa

This command does several things:

  1. Adds the @angular/service-worker package to your project
  2. Configures your app to use service workers
  3. Creates a ngsw-config.json file for configuration
  4. Updates your angular.json to include service worker settings
  5. Adds service worker registration code to your app.module.ts
  6. Creates app icons and updates your manifest.webmanifest file

Step 2: Understanding the Service Worker Configuration

After adding PWA support, you'll find a ngsw-config.json file in your project root. This file controls how the service worker caches and updates your application.

Here's a basic example:

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": [
"/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
}

Let's break down this configuration:

  • index: Specifies the entry point of your application
  • assetGroups: Defines collections of resources that the service worker should manage
    • app group: Critical resources prefetched during installation
    • assets group: Non-critical resources loaded lazily when needed

Step 3: Service Worker Registration

The PWA schematic automatically updates your app.module.ts file to register the service worker:

typescript
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';

@NgModule({
imports: [
BrowserModule,
// Other imports...
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
// Register the ServiceWorker as soon as the application is stable
// or after 30 seconds (whichever comes first).
registrationStrategy: 'registerWhenStable:30000'
})
],
// Other module properties...
})
export class AppModule { }

This configuration:

  • Registers the service worker only in production mode (environment.production)
  • Uses a registration strategy that waits for the application to stabilize

Working with the Service Worker API

Angular provides the SwUpdate and SwPush services to interact with the service worker.

Handling Application Updates

When a new version of your application is deployed, the service worker can detect it and prompt the user to refresh. Here's how to implement this functionality:

typescript
import { Component } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';

@Component({
selector: 'app-root',
template: `
<div *ngIf="updateAvailable" class="update-notification">
New version available!
<button (click)="updateApp()">Update now</button>
</div>
<!-- Rest of your app template -->
`
})
export class AppComponent {
updateAvailable = false;

constructor(private swUpdate: SwUpdate) {
this.swUpdate.available.subscribe(() => {
this.updateAvailable = true;
});
}

updateApp() {
this.swUpdate.activateUpdate().then(() => document.location.reload());
}
}

In this example:

  1. We inject the SwUpdate service
  2. Subscribe to the available observable to know when updates are available
  3. Show a notification to the user
  4. Provide a function to activate the update and reload the page

Checking for Updates Manually

You can also manually check for updates:

typescript
import { Component, OnInit } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
import { interval } from 'rxjs';

@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
constructor(private swUpdate: SwUpdate) {}

ngOnInit() {
if (this.swUpdate.isEnabled) {
// Check for updates every 6 hours
interval(6 * 60 * 60 * 1000).subscribe(() => {
this.swUpdate.checkForUpdate().then(() => {
console.log('Checking for updates');
});
});
}
}
}

Caching HTTP Requests

One of the most powerful features of service workers is their ability to cache HTTP requests. Angular's service worker can be configured to cache API responses, making them available offline.

Data Group Configuration

To cache API responses, add a dataGroups section to your ngsw-config.json:

json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
// Previous asset groups...
],
"dataGroups": [
{
"name": "api-performance",
"urls": [
"/api/products"
],
"cacheConfig": {
"strategy": "performance",
"maxSize": 100,
"maxAge": "1d"
}
},
{
"name": "api-freshness",
"urls": [
"/api/users",
"/api/cart"
],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 20,
"maxAge": "1h",
"timeout": "5s"
}
}
]
}

Angular supports two caching strategies:

  1. Performance strategy: Prioritizes serving from the cache to maximize speed

    • Checks the cache first
    • If the resource exists in the cache, serves it
    • Otherwise, makes a network request
    • Good for relatively static data like product catalogs
  2. Freshness strategy: Prioritizes getting the most up-to-date data

    • Makes a network request first
    • If the request succeeds, serves the network response
    • If the network request fails or times out, serves from the cache
    • Good for frequently updated data like user information

Real-World Example: Building an Offline-Capable News App

Let's create a simple news application that works offline:

Step 1: Set up the service worker configuration

Update your ngsw-config.json to cache news API responses:

json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
// Your asset groups...
],
"dataGroups": [
{
"name": "news-api",
"urls": [
"https://api.example.com/news/**"
],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 50,
"maxAge": "1h",
"timeout": "10s"
}
}
]
}

Step 2: Create a service to fetch news

typescript
// news.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

export interface NewsArticle {
id: number;
title: string;
content: string;
publishedAt: string;
}

@Injectable({
providedIn: 'root'
})
export class NewsService {
private apiUrl = 'https://api.example.com/news';
private cachedNews: NewsArticle[] = [];

constructor(private http: HttpClient) {}

getNews(): Observable<NewsArticle[]> {
return this.http.get<NewsArticle[]>(this.apiUrl).pipe(
tap(news => this.cachedNews = news),
catchError(() => {
console.log('Serving cached news due to network error');
return of(this.cachedNews);
})
);
}
}

Step 3: Create a component to display news

typescript
// news.component.ts
import { Component, OnInit } from '@angular/core';
import { NewsService, NewsArticle } from './news.service';

@Component({
selector: 'app-news',
template: `
<div class="news-container">
<h1>Latest News</h1>
<div class="network-status" *ngIf="!online">
You are offline. Showing cached news.
</div>
<div class="news-list">
<div class="news-item" *ngFor="let article of news">
<h2>{{ article.title }}</h2>
<p class="date">{{ article.publishedAt | date }}</p>
<p>{{ article.content }}</p>
</div>
</div>
</div>
`,
styles: [`
.network-status {
background-color: #ffcc00;
padding: 10px;
margin-bottom: 20px;
border-radius: 4px;
}
.news-item {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
}
`]
})
export class NewsComponent implements OnInit {
news: NewsArticle[] = [];
online = navigator.onLine;

constructor(private newsService: NewsService) {
window.addEventListener('online', () => {
this.online = true;
this.loadNews();
});

window.addEventListener('offline', () => {
this.online = false;
});
}

ngOnInit() {
this.loadNews();
}

loadNews() {
this.newsService.getNews().subscribe(
articles => this.news = articles
);
}
}

This example:

  1. Fetches news from an API
  2. Falls back to in-memory cache if the network request fails
  3. Shows a notification when the user is offline
  4. Automatically refreshes data when the user comes back online

Testing Service Workers

Testing service workers can be challenging since they only activate in production builds. Here's how to test them effectively:

Building for Production

bash
ng build --prod

Serving the Production Build

You can use a simple HTTP server to test your production build:

bash
npx http-server -p 8080 dist/your-app-name

Testing Offline Functionality

  1. Open your application in Chrome
  2. Open Chrome DevTools (F12)
  3. Go to the "Network" tab
  4. Check the "Offline" checkbox
  5. Refresh the page

Your application should continue to work if service workers are properly configured.

Debugging Service Workers

Chrome DevTools provides powerful tools for debugging service workers:

  1. Open Chrome DevTools
  2. Go to the "Application" tab
  3. In the sidebar, expand the "Service Workers" section
  4. You'll see your registered service worker and options to debug it

Common Pitfalls and Solutions

1. Service Worker Not Updating

Problem: Your service worker keeps serving cached content even after you've deployed updates.

Solution:

  • Set appropriate caching strategies in ngsw-config.json
  • Implement version checking as shown in the SwUpdate example
  • Consider using shorter maxAge values for frequently changing resources

2. API Requests Not Being Cached

Problem: Your API responses aren't available offline.

Solution:

  • Verify that your API URLs match the patterns in dataGroups
  • Check for CORS issues - service workers can only cache same-origin requests or cross-origin requests that support CORS
  • Ensure the cacheConfig is properly configured

3. Service Worker Not Registering

Problem: The service worker isn't being registered.

Solution:

  • Ensure you're testing with a production build
  • Verify that HTTPS is being used (service workers require HTTPS except on localhost)
  • Check the browser console for any errors

Summary

Angular's service worker implementation offers a powerful way to enhance your web applications with offline capabilities, faster load times, and improved user experiences. In this tutorial, we've covered:

  • How to add service worker support to your Angular application
  • Configuring the service worker for different caching strategies
  • Handling application updates
  • Implementing offline functionality
  • Testing and debugging service workers

By leveraging these capabilities, you can transform your Angular application into a true Progressive Web App that works reliably regardless of network conditions.

Additional Resources

Exercises

  1. Add service worker support to an existing Angular application
  2. Implement an offline-first strategy for a data-driven application
  3. Create a notification system to alert users when a new version of your app is available
  4. Implement a "Save for offline" feature that explicitly caches selected content
  5. Use the SwPush service to implement push notifications in your application


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)