Angular + PWA: Building an Installable App
Progressive Web Apps (PWAs) bridge the gap between web applications and native apps. Through technologies like Service Workers, the Web App Manifest, and Push Notifications, a PWA delivers experiences comparable to a store-installed app, without the cost and complexity of native distribution. Angular integrates PWA support natively, making the transformation of a web application into an installable app a straightforward and well-documented process.
In this eighth article of the Modern Angular series, we will explore every aspect of building a PWA with Angular: from initial setup to advanced caching management, from offline support to push notifications, through automatic update mechanisms and custom install prompts. We will see how to test and debug every feature using Chrome DevTools.
What You Will Learn
- What PWAs are and their advantages over native apps
- How to set up Angular PWA with
ng add @angular/pwa - Customizing the Web App Manifest for icons, colors, and display modes
- Configuring the Service Worker with
ngsw-config.json - Caching strategies: prefetch, lazy, network-first, cache-first
- Offline support with fallback pages and IndexedDB
- Push notifications with the
SwPushservice - Managing updates with
SwUpdate - Custom install prompt with
beforeinstallprompt - Testing and debugging with Chrome DevTools and Lighthouse
Modern Angular Series
| # | Article | Topic |
|---|---|---|
| 1 | Angular Signals | Fine-grained reactivity |
| 2 | Zoneless Change Detection | Removing Zone.js |
| 3 | New Template Syntax: @if, @for, @defer | Modern control flow |
| 4 | Standalone Components | Module-free architecture |
| 5 | Signal Forms | Reactive forms with Signals |
| 6 | SSR and Incremental Hydration | Server-Side Rendering |
| 7 | Core Web Vitals in Angular | Performance optimization |
| 8 | You are here - Angular PWA | Progressive Web Apps |
| 9 | Advanced Dependency Injection | DI patterns and tokens |
| 10 | Migrating from Angular 17 to 21 | Step-by-step migration |
1. What Are Progressive Web Apps
A Progressive Web App is a web application that uses modern technologies to deliver a user experience comparable to that of a native app. The term "progressive" means the app works for every user, regardless of browser choice, progressively enhancing the experience on browsers that support advanced features.
Key PWA Characteristics
PWAs distinguish themselves from traditional web applications through a set of capabilities that bring them closer to native apps distributed through app stores.
PWA vs Native App vs Traditional Web App
| Feature | Web App | PWA | Native App |
|---|---|---|---|
| Installable | No | Yes | Yes |
| Works offline | No | Yes | Yes |
| Push notifications | No | Yes | Yes |
| Automatic updates | Yes | Yes | Via store |
| Hardware access | Limited | Good | Full |
| Distribution | URL | URL + Install | App Store |
| Development cost | Low | Low | High |
| SEO | Yes | Yes | No (store indexing) |
When to Choose a PWA
PWAs are the ideal choice when you want to reach the largest possible audience with a single codebase. They are perfect for e-commerce, blogs, business dashboards, productivity tools, and any application that benefits from offline access and push notifications. If your app requires deep hardware access (advanced Bluetooth, NFC, specific sensors), a native app may still be necessary.
2. Angular PWA Setup
Angular provides an official schematic that automates PWA configuration. With a single command, the CLI generates all the necessary files: the manifest, service worker configuration, and default icons.
# Add PWA to the project
ng add @angular/pwa
# What gets generated:
# - src/manifest.webmanifest (Web App Manifest)
# - ngsw-config.json (Service Worker configuration)
# - src/assets/icons/ (PWA icons in various sizes)
# - Changes to angular.json (serviceWorker: true)
# - Changes to index.html (link to manifest, meta theme-color)
# - Changes to app.config.ts (ServiceWorkerModule / provideServiceWorker)
Configuration in app.config.ts
After running the schematic, the app.config.ts file is automatically updated
to register the service worker. Registration only happens in production and after the
application has stabilized.
// app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideServiceWorker } from '@angular/service-worker';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
provideServiceWorker('ngsw-worker.js', {
enabled: !isDevMode(),
// Register SW after app stabilization
registrationStrategy: 'registerWhenStable:30000'
})
]
};
Service Worker and Development Environment
The service worker is disabled in development mode (isDevMode()) because
aggressive caching would interfere with hot-reload and live-reloading. To test the
service worker locally, you need to run a production build with ng build
and serve the static files with an HTTP server like http-server or
npx serve.
3. Web App Manifest
The Web App Manifest is a JSON file that describes your application to the browser and operating system. It contains information such as the app name, icons, theme colors, and display mode. Without a valid manifest, the browser will not offer the option to install the app.
{
"name": "My Angular PWA App",
"short_name": "MyApp",
"description": "A complete Angular PWA application with offline support",
"theme_color": "#58a6ff",
"background_color": "#0d1117",
"display": "standalone",
"orientation": "portrait-primary",
"scope": "/",
"start_url": "/",
"id": "/",
"categories": ["productivity", "utilities"],
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
],
"screenshots": [
{
"src": "assets/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide",
"label": "Main dashboard"
},
{
"src": "assets/screenshots/mobile.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow",
"label": "Mobile view"
}
],
"shortcuts": [
{
"name": "Dashboard",
"short_name": "Dash",
"url": "/dashboard",
"icons": [{ "src": "assets/icons/shortcut-dashboard.png", "sizes": "96x96" }]
},
{
"name": "Settings",
"short_name": "Settings",
"url": "/settings",
"icons": [{ "src": "assets/icons/shortcut-settings.png", "sizes": "96x96" }]
}
]
}
Manifest Display Modes
| Mode | Description | Use Case |
|---|---|---|
standalone |
App appears as a native app without address bar | Most common choice for PWAs |
fullscreen |
App takes up the entire screen with no browser UI | Games, immersive presentations |
minimal-ui |
Similar to standalone but with minimal navigation controls | Apps that need back/forward navigation |
browser |
App opens in a standard browser tab | Traditional websites (not recommended for PWAs) |
4. Service Worker Configuration
The ngsw-config.json file is the heart of PWA configuration in Angular.
It defines which resources are cached, how they are updated, and with which strategy.
Angular automatically generates the ngsw-worker.js file during the
production build based on this configuration.
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app-shell",
"installMode": "prefetch",
"updateMode": "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)"
]
}
}
],
"dataGroups": [
{
"name": "api-freshness",
"urls": ["/api/user/**", "/api/notifications/**"],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 50,
"maxAge": "1h",
"timeout": "5s"
}
},
{
"name": "api-performance",
"urls": ["/api/articles/**", "/api/products/**"],
"cacheConfig": {
"strategy": "performance",
"maxSize": 100,
"maxAge": "1d"
}
}
],
"navigationUrls": [
"/**",
"!/__**"
]
}
assetGroups vs dataGroups
| Property | assetGroups | dataGroups |
|---|---|---|
| Purpose | Static app resources (JS, CSS, images) | Dynamic API data |
| Versioning | Based on file hash | Based on time (maxAge) |
| installMode | prefetch or lazy |
N/A |
| strategy | N/A | freshness or performance |
| Update trigger | When app version changes | Based on maxAge and timeout |
5. Caching Strategies
Choosing the right caching strategy is fundamental for balancing performance and data
freshness. Angular NGSW supports two main strategies for dataGroups:
freshness (network-first) and performance (cache-first),
plus static caching for assets.
Caching Strategies in Detail
| Strategy | Priority | Behavior | Use Case |
|---|---|---|---|
| prefetch (assetGroups) | Cache | Downloads all resources immediately at SW installation | App shell, main JS and CSS |
| lazy (assetGroups) | Cache on demand | Caches the resource only when it is first requested | Images, fonts, secondary assets |
| freshness (dataGroups) | Network | Tries the network first; if timeout expires, falls back to cache | User profile, notifications, real-time data |
| performance (dataGroups) | Cache | Uses cache if available and not expired; otherwise network | Blog articles, product catalog, static data |
The freshness strategy is equivalent to the network-first pattern: the service worker tries the network first and, if the request does not succeed within the specified timeout, serves the cached version. The performance strategy is cache-first: it immediately serves the cached version (if not expired) and updates the cache in the background.
Stale-While-Revalidate with Angular
Angular NGSW does not natively support the stale-while-revalidate strategy,
but you can simulate it by combining the performance strategy with a
short maxAge. For example, setting "maxAge": "5m" will serve
data from cache immediately, but after 5 minutes will make a new network request on the
next access. If you need more granular control, consider using the
Cache API directly with a custom service worker.
6. Offline Support
One of the most important features of a PWA is the ability to work without an internet connection. Angular automatically handles caching of static resources through the service worker, but for a complete offline experience you also need to manage connection state, fallback pages, and local data persistence.
Connection State Detection
// network-status.service.ts
import { Injectable, signal, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class NetworkStatusService {
private platformId = inject(PLATFORM_ID);
isOnline = signal(true);
connectionType = signal<string>('unknown');
constructor() {
if (isPlatformBrowser(this.platformId)) {
this.isOnline.set(navigator.onLine);
window.addEventListener('online', () => {
this.isOnline.set(true);
this.syncPendingData();
});
window.addEventListener('offline', () => {
this.isOnline.set(false);
});
// Network Information API (if available)
const connection = (navigator as any).connection;
if (connection) {
this.connectionType.set(connection.effectiveType);
connection.addEventListener('change', () => {
this.connectionType.set(connection.effectiveType);
});
}
}
}
private async syncPendingData(): Promise<void> {
// Sync locally saved data when back online
const pendingActions = await this.getPendingActions();
for (const action of pendingActions) {
try {
await fetch(action.url, action.options);
await this.removePendingAction(action.id);
} catch (err) {
console.error('Sync failed for:', action.id);
}
}
}
private async getPendingActions(): Promise<any[]> {
// Retrieve pending actions from IndexedDB
return [];
}
private async removePendingAction(id: string): Promise<void> {
// Remove completed action from IndexedDB
}
}
Data Persistence with IndexedDB
For managing structured offline data, IndexedDB is the most robust solution.
Unlike localStorage (synchronous, 5-10 MB limit), IndexedDB supports large
data volumes, indexed queries, and asynchronous operations.
// offline-storage.service.ts
import { Injectable, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class OfflineStorageService {
private db: IDBDatabase | null = null;
private platformId = inject(PLATFORM_ID);
private readonly DB_NAME = 'pwa-offline-store';
private readonly DB_VERSION = 1;
async init(): Promise<void> {
if (!isPlatformBrowser(this.platformId)) return;
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
// Create object stores for offline data
if (!db.objectStoreNames.contains('articles')) {
db.createObjectStore('articles', { keyPath: 'id' });
}
if (!db.objectStoreNames.contains('pendingActions')) {
const store = db.createObjectStore('pendingActions', {
keyPath: 'id', autoIncrement: true
});
store.createIndex('timestamp', 'timestamp');
}
};
request.onsuccess = (event) => {
this.db = (event.target as IDBOpenDBRequest).result;
resolve();
};
request.onerror = () => reject(request.error);
});
}
async save<T>(storeName: string, data: T): Promise<void> {
if (!this.db) await this.init();
return new Promise((resolve, reject) => {
const tx = this.db!.transaction(storeName, 'readwrite');
tx.objectStore(storeName).put(data);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async getAll<T>(storeName: string): Promise<T[]> {
if (!this.db) await this.init();
return new Promise((resolve, reject) => {
const tx = this.db!.transaction(storeName, 'readonly');
const request = tx.objectStore(storeName).getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
Offline Storage Limitations
- Browser quota: Browsers limit available storage (typically 50-80% of disk space). Use
navigator.storage.estimate()to check available space - Eviction policy: When space is insufficient, the browser may delete data. Request persistent storage with
navigator.storage.persist() - SSR safety: IndexedDB does not exist on the server side. Always use
isPlatformBrowserbefore accessing it - Sync conflicts: When the user comes back online, local data may conflict with server data. Implement a conflict resolution strategy
7. Push Notifications
Push notifications allow your PWA to send messages to the user even when the app is not
active. Angular provides the SwPush service that simplifies managing
subscriptions and receiving notifications through the Web Push Protocol.
Subscribing to Push Notifications
// push-notification.service.ts
import { Injectable, inject } from '@angular/core';
import { SwPush } from '@angular/service-worker';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class PushNotificationService {
private swPush = inject(SwPush);
private http = inject(HttpClient);
// VAPID public key (generated on server)
private readonly VAPID_PUBLIC_KEY = 'BPxGEaVr...your-public-key...';
async subscribeToNotifications(): Promise<void> {
try {
// Request permission and obtain subscription
const subscription = await this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
});
console.log('Push subscription:', JSON.stringify(subscription));
// Send subscription to backend
await this.http.post('/api/push/subscribe', subscription).toPromise();
} catch (err) {
console.error('Push subscription error:', err);
}
}
listenForNotifications(): void {
// Notifications received while app is in foreground
this.swPush.messages.subscribe((message: any) => {
console.log('Notification received:', message);
// Update the UI with new data
});
// Click on notification (opens/focuses the app)
this.swPush.notificationClicks.subscribe((event) => {
console.log('Notification click:', event.action, event.notification);
// Navigate to the appropriate page
const url = event.notification.data?.url;
if (url) {
window.open(url, '_blank');
}
});
}
async unsubscribe(): Promise<void> {
try {
const subscription = await this.swPush.subscription.toPromise();
if (subscription) {
await subscription.unsubscribe();
await this.http.post('/api/push/unsubscribe', {
endpoint: subscription.endpoint
}).toPromise();
}
} catch (err) {
console.error('Unsubscribe error:', err);
}
}
}
Sending Notifications from the Server
// server/push-server.ts (Node.js with web-push)
import webPush from 'web-push';
// VAPID configuration
webPush.setVapidDetails(
'mailto:info@example.com',
process.env.VAPID_PUBLIC_KEY!,
process.env.VAPID_PRIVATE_KEY!
);
async function sendNotification(
subscription: webPush.PushSubscription,
payload: object
): Promise<void> {
const notification = {
notification: {
title: 'New article available!',
body: 'Discover the latest on Angular PWA',
icon: '/assets/icons/icon-192x192.png',
badge: '/assets/icons/badge-72x72.png',
vibrate: [100, 50, 100],
data: {
url: '/blog/angular-pwa',
dateOfArrival: Date.now()
},
actions: [
{ action: 'read', title: 'Read Now' },
{ action: 'close', title: 'Close' }
]
}
};
try {
await webPush.sendNotification(
subscription,
JSON.stringify(notification)
);
} catch (error: any) {
if (error.statusCode === 410) {
// Subscription expired: remove it from database
await removeSubscription(subscription.endpoint);
}
}
}
VAPID Keys
VAPID keys (Voluntary Application Server Identification) are used to
authenticate your server with the browser's push service. You can generate them with
the command npx web-push generate-vapid-keys. The public key goes in the
frontend, the private key stays exclusively on the server. Never commit the private
key to your repository.
8. Managing App Updates
When you release a new version of your PWA, the service worker needs to download the
updated resources and notify the user. Angular provides the SwUpdate service
to manage the update lifecycle in a controlled manner.
// app-update.service.ts
import {
Injectable, inject, ApplicationRef
} from '@angular/core';
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
import { filter, first, interval, concat } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AppUpdateService {
private swUpdate = inject(SwUpdate);
private appRef = inject(ApplicationRef);
initUpdateCheck(): void {
if (!this.swUpdate.isEnabled) {
console.log('Service Worker not enabled');
return;
}
// Check for updates every 6 hours
const appIsStable$ = this.appRef.isStable.pipe(
first(stable => stable)
);
const every6Hours$ = interval(6 * 60 * 60 * 1000);
const every6HoursOnceAppStable$ = concat(appIsStable$, every6Hours$);
every6HoursOnceAppStable$.subscribe(async () => {
try {
const updateFound = await this.swUpdate.checkForUpdate();
console.log(updateFound
? 'New update found!'
: 'App is already up to date');
} catch (err) {
console.error('Update check error:', err);
}
});
// Listen for when a new version is ready
this.swUpdate.versionUpdates.pipe(
filter((event): event is VersionReadyEvent =>
event.type === 'VERSION_READY'
)
).subscribe(event => {
console.log('Current version:', event.currentVersion.hash);
console.log('New version:', event.latestVersion.hash);
this.promptUpdate();
});
// Handle unrecoverable SW errors
this.swUpdate.unrecoverable.subscribe(event => {
console.error('SW in unrecoverable state:', event.reason);
// Force a full reload
document.location.reload();
});
}
private promptUpdate(): void {
const shouldUpdate = confirm(
'A new version of the app is available. ' +
'Would you like to update now?'
);
if (shouldUpdate) {
// Activate new version and reload
this.swUpdate.activateUpdate().then(() => {
document.location.reload();
});
}
}
}
PWA Update Lifecycle
| Phase | SwUpdate Event | Description |
|---|---|---|
| 1. Detection | VERSION_DETECTED |
The SW found a new version on the server |
| 2. Download | VERSION_INSTALLATION_FAILED |
Error during new version download (if failed) |
| 3. Ready | VERSION_READY |
New version is downloaded and ready for activation |
| 4. Activation | activateUpdate() |
User confirms and SW activates the new version |
| 5. Reload | location.reload() |
Page reloads with updated resources |
Unrecoverable Service Worker Error
The unrecoverable event indicates that the service worker can no longer
correctly serve the current version of the app (for example, the server has removed
resources that are still needed). In this case, the only solution is to force a full
page reload with document.location.reload(). Monitor this event in
production to identify deployment issues.
9. Custom Install Prompt
Browsers automatically display an installation banner when the PWA meets the installability
criteria. However, the native banner offers limited visibility and control. By intercepting
the beforeinstallprompt event, you can create a custom installation experience
with a dedicated button and a design consistent with your app.
// pwa-install.component.ts
import {
Component, signal, inject, PLATFORM_ID, afterNextRender
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Component({
selector: 'app-pwa-install',
standalone: true,
template: `
<div class="install-banner">
<p>Install our app for a better experience!</p>
<button (click)="installApp()">Install</button>
<button (click)="dismissBanner()">Not now</button>
</div>
`
})
export class PwaInstallComponent {
private platformId = inject(PLATFORM_ID);
showInstallBanner = signal(false);
isAppInstalled = signal(false);
private deferredPrompt: any = null;
constructor() {
afterNextRender(() => {
this.setupInstallPrompt();
this.checkIfInstalled();
});
}
private setupInstallPrompt(): void {
if (!isPlatformBrowser(this.platformId)) return;
// Intercept the beforeinstallprompt event
window.addEventListener('beforeinstallprompt', (event: Event) => {
event.preventDefault();
this.deferredPrompt = event;
this.showInstallBanner.set(true);
});
// Detect when the app is installed
window.addEventListener('appinstalled', () => {
this.isAppInstalled.set(true);
this.showInstallBanner.set(false);
this.deferredPrompt = null;
console.log('PWA installed successfully!');
});
}
async installApp(): Promise<void> {
if (!this.deferredPrompt) return;
// Show the native install prompt
this.deferredPrompt.prompt();
const result = await this.deferredPrompt.userChoice;
console.log('User choice:', result.outcome);
if (result.outcome === 'accepted') {
console.log('Installation accepted');
} else {
console.log('Installation dismissed');
}
this.deferredPrompt = null;
this.showInstallBanner.set(false);
}
dismissBanner(): void {
this.showInstallBanner.set(false);
}
private checkIfInstalled(): void {
// Check if app is already in standalone mode
const isStandalone = window.matchMedia(
'(display-mode: standalone)'
).matches;
this.isAppInstalled.set(isStandalone);
}
}
PWA Installability Criteria
| Requirement | Description |
|---|---|
| HTTPS | The app must be served over HTTPS (or localhost for dev) |
| Web App Manifest | Must include name, icons (192px + 512px), start_url, display |
| Service Worker | An active service worker with a fetch event handler |
| Not already installed | The app must not already be installed on the device |
| User engagement | The user must have interacted with the domain (click, scroll, etc.) |
10. Testing and Debugging
Testing a PWA requires specific tools because the service worker is only active in
production and PWA features are not available during development with
ng serve. Chrome DevTools and Lighthouse provide everything needed to
verify and debug every aspect of your PWA.
Local Build and Serving for Testing
# 1. Production build
ng build
# 2. Install a static HTTP server (one time)
npm install -g http-server
# 3. Serve the production build
cd dist/your-app/browser
http-server -p 8080 -c-1
# Or with npx (no global install)
npx serve dist/your-app/browser -l 8080
# 4. Open browser at http://localhost:8080
# 5. Open DevTools > Application tab
Chrome DevTools - Application Tab
The Application tab in Chrome DevTools is the main panel for inspecting and debugging all aspects of a PWA.
DevTools Panels for PWA
| Panel | Function | Verification |
|---|---|---|
| Manifest | Shows the parsed manifest | Name, icons, display mode, start URL are correct |
| Service Workers | Status of registered SWs | SW is active, pending updates, errors |
| Cache Storage | Cache contents | Correct resources are cached, total sizes |
| IndexedDB | Local databases | Offline data persisted correctly |
| Storage | Storage quota and usage | Space used vs available |
Lighthouse PWA Audit
Lighthouse includes a dedicated section for PWA validation. The audit verifies installability criteria, offline optimizations, and best practices. A well-configured PWA should achieve a Lighthouse PWA score of 100/100.
Lighthouse PWA Checklist
- The service worker registers a
fetchhandler - The page responds with a 200 status code when offline
start_urlin the manifest is reachable offline- The manifest has
nameorshort_name - The manifest has icons of at least 192px and 512px
- The manifest specifies
display(standalone/fullscreen/minimal-ui) - Content is sized appropriately for the viewport
- Meta tag
theme-coloris present in the HTML - The page has a valid
viewportmeta tag - The site uses HTTPS
- HTTP is redirected to HTTPS
Service Worker Debugging
// In the browser, via DevTools Console:
// 1. Check service worker status
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => {
console.log('SW Scope:', reg.scope);
console.log('SW State:', reg.active?.state);
console.log('SW Waiting:', reg.waiting);
console.log('SW Installing:', reg.installing);
});
});
// 2. Force service worker update
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => reg.update());
});
// 3. Unregister all service workers (for full reset)
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => reg.unregister());
});
// 4. Inspect service worker cache
caches.keys().then(names => {
names.forEach(name => {
caches.open(name).then(cache => {
cache.keys().then(requests => {
console.log(`Cache "${name}":`, requests.length, 'entries');
requests.forEach(req => console.log(' -', req.url));
});
});
});
});
// 5. Simulate offline state via DevTools
// Network tab > select "Offline" in throttling dropdown
// Or: Application > Service Workers > check "Offline"
Common Issues and Solutions
- SW does not update: Use "Update on reload" in DevTools > Application > Service Workers during development
- Stale cache persists: Clear all storage from DevTools > Application > Storage > "Clear site data"
- Install prompt does not appear: Verify that all installability criteria are met in the Manifest panel
- Push notifications not arriving: Check that permission is "granted" with
Notification.permissionand that the subscription is valid - CORS errors in cache: External resources must be served with appropriate CORS headers to be cached by the SW
- SW stuck in "waiting" state: The new SW waits for all tabs of the app to close. Use
skipWaiting()to force immediate activation
Summary and Next Steps
Progressive Web Apps represent a natural evolution of web applications, offering features that until recently were exclusive to native apps. Angular greatly simplifies PWA creation through its built-in support for service workers, intelligent caching, push notifications, and managed updates.
Key Concepts from This Article
- PWA: Installable web applications with offline support, push notifications, and automatic updates
- Setup:
ng add @angular/pwagenerates manifest, SW configuration, and icons - Manifest: JSON file describing the app (name, icons, display mode, start URL)
- ngsw-config.json: Configuration of assetGroups (static resources) and dataGroups (dynamic API data)
- Caching: prefetch/lazy strategies for assets, freshness/performance for API data
- Offline: Network state detection, IndexedDB for persistence, sync when back online
- Push:
SwPushfor subscriptions and receiving notifications, VAPID for server authentication - Updates:
SwUpdatefor checking, notifying, and activating new versions - Install prompt:
beforeinstallpromptfor custom installation UI - Testing: Chrome DevTools Application tab, Lighthouse PWA audit, SW debugging
In the next article of the series, we will explore Advanced Dependency Injection
in Angular, analyzing custom injection tokens, tree-shakable providers, multi-providers,
factory providers, and scoping strategies with providedIn.
Related deep dive: 7 - Ottimizzare Core Web Vitals in Angular.
Related deep dive: 9 - Dependency Injection Avanzata in Angular.
Frequently asked questions
What is a Progressive Web App?
A Progressive Web App (PWA) is a web application that uses modern technologies to deliver a user experience comparable to that of a native app.
How does Angular simplify the process of creating a PWA?
Angular simplifies the process of creating a PWA through its built-in support for service workers, intelligent caching, push notifications, and managed updates.
What is the purpose of the Web App Manifest in a PWA?
The Web App Manifest is a JSON file that describes your application to the browser and operating system, containing information such as app name, icons, theme colors, and display mode.
How can you test and debug a PWA using Chrome DevTools?
You can test and debug a PWA using Chrome DevTools by inspecting and debugging all aspects of the PWA in the Application tab.
Commenti
Caricamento commenti...
Sign in to comment