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:
ng add @angular/pwa
This command does several things:
- Adds the
@angular/service-worker
package to your project - Configures your app to use service workers
- Creates a
ngsw-config.json
file for configuration - Updates your
angular.json
to include service worker settings - Adds service worker registration code to your
app.module.ts
- 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:
{
"$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:
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:
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:
- We inject the
SwUpdate
service - Subscribe to the
available
observable to know when updates are available - Show a notification to the user
- Provide a function to activate the update and reload the page
Checking for Updates Manually
You can also manually check for updates:
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
:
{
"$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:
-
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
-
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:
{
"$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
// 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
// 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:
- Fetches news from an API
- Falls back to in-memory cache if the network request fails
- Shows a notification when the user is offline
- 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
ng build --prod
Serving the Production Build
You can use a simple HTTP server to test your production build:
npx http-server -p 8080 dist/your-app-name
Testing Offline Functionality
- Open your application in Chrome
- Open Chrome DevTools (F12)
- Go to the "Network" tab
- Check the "Offline" checkbox
- 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:
- Open Chrome DevTools
- Go to the "Application" tab
- In the sidebar, expand the "Service Workers" section
- 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
- Angular Service Worker Introduction
- Service Worker Configuration
- MDN Web Docs: Service Worker API
- Google Developers: Service Workers
Exercises
- Add service worker support to an existing Angular application
- Implement an offline-first strategy for a data-driven application
- Create a notification system to alert users when a new version of your app is available
- Implement a "Save for offline" feature that explicitly caches selected content
- 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! :)