Angular + PWA: maak een installeerbare app
Le Progressieve webapps (PWA) zij vertegenwoordigen de brug tussen webapplicaties en native apps. Dankzij technologieën zoals i Dienstverleners, De Webapp-manifest en de Pushmelding, een PWA-aanbiedingen ervaringen vergelijkbaar met die van een app geïnstalleerd vanuit de winkel, maar dan zonder de kosten en complexiteit van de inheemse distributie. Angular integreert native PWA-ondersteuning, waardoor het transformeren van een webapplicatie naar een installeerbare app een proces wordt eenvoudig en goed gedocumenteerd.
In dit achtste artikel uit de serie Moderne hoekige we zullen elk aspect onderzoeken van het creëren van een PWA met Angular: van initiële configuratie tot geavanceerd beheer cache, van offline ondersteuning tot pushmeldingen en updatemechanismen automatische en aangepaste installatieprompt. We zullen bekijken hoe we ze allemaal kunnen testen en debuggen functionaliteit met Chrome DevTools-tools.
Wat u in dit artikel leert
- Wat zijn PWA’s en welke voordelen bieden ze ten opzichte van native apps?
- Hoe Angular PWA in te stellen met
ng add @angular/pwa - Het Web App Manifest aanpassen voor pictogrammen, kleuren en weergavemodi
- De servicemedewerker configureren met
ngsw-config.json - Cachingstrategieën: prefetch, lui, netwerk eerst, cache eerst
- Offline ondersteuning met reservepagina's en IndexedDB
- Pushmelding bij de service
SwPush - Beheer updates met
SwUpdate - Installeer een aangepaste prompt met
beforeinstallprompt - Testen en debuggen met Chrome DevTools en Lighthouse
Overzicht moderne hoekige series
| # | Item | Focus |
|---|---|---|
| 1 | Hoekige signalen | Fijnkorrelig reactievermogen |
| 2 | Zoneloze wijzigingsdetectie | Verwijder Zone.js |
| 3 | Nieuwe sjablonen @if, @for, @defer | Moderne controlestroom |
| 4 | Op zichzelf staande componenten | Architectuur zonder NgModule |
| 5 | Signaalformulieren | Responsieve formulieren met signalen |
| 6 | SSR en incrementele hydratatie | Rendering op de server |
| 7 | Kernwebvitalen in Angular | Prestaties en statistieken |
| 8 | Je bent hier - Hoekige PWA | Progressieve webapps |
| 9 | Geavanceerde afhankelijkheidsinjectie | DI boomschudbaar |
| 10 | Migreer van Angular 17 naar 21 | Migratie gids |
1. Wat zijn progressieve webapps
Una Progressieve webapps is een webapplicatie die gebruik maakt van technologieën modern om een gebruikerservaring te bieden die vergelijkbaar is met die van een native app. De termijn "progressief" betekent dat de app voor elke gebruiker werkt, ongeacht de browser gebruikt, waardoor de ervaring geleidelijk wordt verbeterd in browsers die de geavanceerde functies.
Belangrijkste kenmerken van PWA's
PWA's onderscheiden zich van traditionele webapplicaties door een reeks mogelijkheden ze brengen ze dichter bij native apps die via winkels worden gedistribueerd.
PWA versus native app versus traditionele webapp
| Kenmerkend | Web-app | PWA | Native app |
|---|---|---|---|
| Installeerbaar | No | Si | Si |
| Werkt offline | No | Si | Si |
| Pushmelding | No | Si | Si |
| Automatische update | Si | Si | Via winkel |
| Hardwaretoegang | Beperkt | Goed | Compleet |
| Verdeling | URL | URL + Installeren | App Store |
| Ontwikkelingskosten | Bas | Bas | Hoog |
| SEO | Si | Si | Nee (winkelindexering) |
Wanneer moet u een PWA kiezen?
PWA's zijn de ideale keuze als u het grootste aantal gebruikers wilt bereiken één enkele codebasis. Ze zijn perfect voor e-commerce, blogs, bedrijfsdashboards en tools productiviteit en elke applicatie die profiteert van offline toegang en meldingen duwen. Als de app diepe toegang tot hardware vereist (geavanceerde Bluetooth, NFC, sensoren bijzonderheden), kan een native app nog steeds nodig zijn.
2. Hoekige PWA instellen
Angular biedt een officieel schema dat de PWA-configuratie automatiseert. Met een enkele opdracht genereert de CLI alle benodigde bestanden: het manifest, de configuratie van de servicemedewerker en standaardpictogrammen.
# Aggiungere PWA al progetto
ng add @angular/pwa
# Cosa viene generato:
# - src/manifest.webmanifest (Web App Manifest)
# - ngsw-config.json (configurazione Service Worker)
# - src/assets/icons/ (icone PWA in varie dimensioni)
# - Modifiche a angular.json (serviceWorker: true)
# - Modifiche a index.html (link al manifest, meta theme-color)
# - Modifiche a app.config.ts (ServiceWorkerModule / provideServiceWorker)
Configuratie in app.config.ts
Na het uitvoeren van het schema, wordt de app.config.ts wordt bijgewerkt
automatisch om de servicemedewerker te registreren. Registratie vindt uitsluitend plaats in
productie en na toepassing stabilisatie.
// 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(),
// Registra il SW dopo la stabilizzazione dell'app
registrationStrategy: 'registerWhenStable:30000'
})
]
};
Servicemedewerker en ontwikkelingsomgeving
De servicemedewerker is uitgeschakeld in de ontwikkelingsmodus (isDevMode()) waarom
agressieve caching zou het hot-reloaden en live-reloaden verstoren. Om te testen
de servicemedewerker ter plaatse, waarmee u een productiebuild moet uitvoeren
ng build en serveer de statische bestanden met een HTTP-server zoals
http-server o npx serve.
3. Webapp-manifest
Il Webapp-manifest is een JSON-bestand dat uw toepassing beschrijft browser en besturingssysteem. Bevat informatie zoals app-naam, pictogrammen, de themakleuren en weergavemodus. Zonder een geldig manifest kan de browser biedt niet de mogelijkheid om de app te installeren.
{
"name": "La Mia App Angular PWA",
"short_name": "MiaApp",
"description": "Un'applicazione Angular PWA completa con supporto offline",
"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": "Dashboard principale"
},
{
"src": "assets/screenshots/mobile.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow",
"label": "Vista mobile"
}
],
"shortcuts": [
{
"name": "Dashboard",
"short_name": "Dash",
"url": "/dashboard",
"icons": [{ "src": "assets/icons/shortcut-dashboard.png", "sizes": "96x96" }]
},
{
"name": "Impostazioni",
"short_name": "Settings",
"url": "/settings",
"icons": [{ "src": "assets/icons/shortcut-settings.png", "sizes": "96x96" }]
}
]
}
Duidelijke weergavemodus
| Modus | Beschrijving | Gebruikscasus |
|---|---|---|
standalone |
De app verschijnt als een native app zonder adresbalk | De meest voorkomende keuze voor PWA |
fullscreen |
De app neemt het volledige scherm in beslag, zonder browserinterface | Games, meeslepende presentaties |
minimal-ui |
Vergelijkbaar met standalone, maar met minimale navigatieknoppen | Apps die achteruit/vooruit-navigatie vereisen |
browser |
De app wordt geopend in een standaard browsertabblad | Traditionele websites (niet aanbevolen voor PWA) |
4. De servicemedewerker configureren
Het bestand ngsw-config.json is het hart van de PWA-configuratie in Angular.
Definieert welke bronnen in de cache worden opgeslagen, hoe ze worden bijgewerkt en waarmee
strategie. Angular genereert het bestand automatisch ngsw-worker.js tijdens
de productieopbouw op basis van deze configuratie.
{
"$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 versus dataGroups
| Eigendom | activaGroepen | gegevensGroepen |
|---|---|---|
| Domein | Statische app-bronnen (JS, CSS, afbeeldingen) | Dynamische gegevens uit API |
| Versiebeheer | Gebaseerd op de bestandshash | Op tijd gebaseerd (maxAge) |
| installatiemodus | prefetch o lazy |
N.v.t |
| strategie | N.v.t | freshness o performance |
| Update | Wanneer de app-versie verandert | Gebaseerd op maxAge en time-out |
5. Cachingstrategieën
De keuze van de cachingstrategie is essentieel om de prestaties en prestaties in evenwicht te brengen
versheid van gegevens. Angular NGSW ondersteunt twee hoofdstrategieën voor i
dataGroups: versheid (netwerk eerst) e
prestatie (cache-first), evenals statische caching voor assets.
Cachingstrategieën in detail
| Strategie | Prioriteit | Gedrag | Gebruikscasus |
|---|---|---|---|
| vooraf ophalen (assetGroups) | Cache | Download alle bronnen onmiddellijk na installatie van de SW | Shell-apps, kern-JS en CSS |
| lui (assetGroups) | Caching op aanvraag | Slaat de bron alleen op in het cachegeheugen wanneer daarom wordt gevraagd | Afbeeldingen, lettertypen, secundaire middelen |
| versheid (dataGroepen) | Netwerk | Probeer het netwerk; als de time-out is verstreken, gebruik dan cache | Gebruikersprofiel, meldingen, realtime gegevens |
| prestatie (dataGroepen) | Cache | Gebruik cache indien beschikbaar en niet verlopen; anders netwerk | Blogartikelen, productcatalogus, statische gegevens |
De strategie versheid is gelijk aan het patroon netwerk eerst: de servicemedewerker probeert eerst het netwerk en, als het verzoek binnen de time-out mislukt, opgegeven, is de cacheversie nodig. De strategie prestatie het is in plaats daarvan cache-eerst: heb de cacheversie onmiddellijk nodig (indien niet verlopen) en ververs de cache op de achtergrond.
Verouderd-terwijl-revalidatie met Angular
Angular NGSW ondersteunt de strategie niet standaard oud-terwijl-revalideren,
maar je kunt het simuleren door de strategie te combineren prestatie met een
maxAge kort. Instellen bijvoorbeeld "maxAge": "5m" de cache
het zal de gegevens onmiddellijk verwerken, maar na 5 minuten zal het een nieuw verzoek indienen bij het netwerk
bij de volgende login. Als u meer gedetailleerde controle nodig heeft, kunt u overwegen deze te gebruiken
direct van Cache-API met een douanedienstmedewerker.
6. Offline-ondersteuning
Een van de belangrijkste kenmerken van een PWA is de mogelijkheid om zonder te werken internetverbinding. Angular verwerkt automatisch het cachen van bronnen statisch via de servicemedewerker, maar voor een volledige offline ervaring is dit noodzakelijk beheer ook de verbindingsstatus, reservepagina's en serverpersistentie lokale gegevens.
Detectie van verbindingsstatus
// 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 (se disponibile)
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> {
// Sincronizza i dati salvati localmente quando torna 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 fallita per:', action.id);
}
}
}
private async getPendingActions(): Promise<any[]> {
// Recupera da IndexedDB le azioni in attesa
return [];
}
private async removePendingAction(id: string): Promise<void> {
// Rimuovi azione completata da IndexedDB
}
}
Gegevenspersistentie met IndexedDB
Om gestructureerde gegevens offline te beheren, IndexedDB het is de meest robuuste oplossing.
In tegenstelling tot localStorage (synchroon, limiet 5-10 MB), IndexedDB ondersteunt
grote hoeveelheden gegevens, geïndexeerde zoekopdrachten en asynchrone bewerkingen.
// 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;
// Crea object stores per i dati offline
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);
});
}
}
Beperkingen van offline opslag
- Browser delen: Browsers beperken de beschikbare opslagruimte (doorgaans 50-80% van de schijfruimte). VS
navigator.storage.estimate()om de beschikbare ruimte te controleren - Uitzettingsbeleid: Als er onvoldoende ruimte is, kan de browser de gegevens verwijderen. Vraag permanente opslag aan met
navigator.storage.persist() - SSR-veiligheid: IndexedDB bestaat niet op de server. Altijd gebruiken
isPlatformBrowservoordat u er toegang toe krijgt - Synchronisatieconflicten: Wanneer de gebruiker weer online komt, kunnen de lokale gegevens conflicteren met de servergegevens. Implementeer een strategie voor conflictoplossing
7. Pushmelding
Met pushmeldingen kan uw PWA zelfs op dat moment berichten naar de gebruiker sturen
de app is niet actief. Angular levert de service SwPush wat vereenvoudigt
het beheren van uw abonnementen en het ontvangen van meldingen via de
Web-push-protocol.
Abonnement op pushmeldingen
// 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);
// Chiave pubblica VAPID (generata sul server)
private readonly VAPID_PUBLIC_KEY = 'BPxGEaVr...tua-chiave-pubblica...';
async subscribeToNotifications(): Promise<void> {
try {
// Richiedi il permesso e ottieni la sottoscrizione
const subscription = await this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
});
console.log('Sottoscrizione push:', JSON.stringify(subscription));
// Invia la sottoscrizione al backend
await this.http.post('/api/push/subscribe', subscription).toPromise();
} catch (err) {
console.error('Errore sottoscrizione push:', err);
}
}
listenForNotifications(): void {
// Notifiche ricevute mentre l'app e in primo piano
this.swPush.messages.subscribe((message: any) => {
console.log('Notifica ricevuta:', message);
// Aggiorna la UI con i nuovi dati
});
// Clic sulla notifica (apre/focalizza l'app)
this.swPush.notificationClicks.subscribe((event) => {
console.log('Clic su notifica:', event.action, event.notification);
// Naviga alla pagina appropriata
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('Errore cancellazione sottoscrizione:', err);
}
}
}
Meldingen verzenden vanaf de server
// server/push-server.ts (Node.js con web-push)
import webPush from 'web-push';
// Configurazione VAPID
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: 'Nuovo articolo disponibile!',
body: 'Scopri le novità su 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: 'leggi', title: 'Leggi Ora' },
{ action: 'chiudi', title: 'Chiudi' }
]
}
};
try {
await webPush.sendNotification(
subscription,
JSON.stringify(notification)
);
} catch (error: any) {
if (error.statusCode === 410) {
// Sottoscrizione scaduta: rimuovila dal database
await removeSubscription(subscription.endpoint);
}
}
}
VAPID-sleutels
De VAPID-sleutels (Vrijwillige identificatie van de applicatieserver) worden gebruikt
authenticeer uw server bij de browserpushservice. Je kunt ze genereren met de
opdracht npx web-push generate-vapid-keys. De publieke sleutel moet worden ingevoerd
in de frontend blijft de privé uitsluitend op de server. Bega nooit de
privésleutel in de repository.
8. Updatebeheer
Wanneer u een nieuwe versie van uw PWA vrijgeeft, moet de servicemedewerker de
bijgewerkte bronnen en stel de gebruiker hiervan op de hoogte. Angular levert de service
SwUpdate om de updatelevenscyclus op een bepaalde manier te beheren
gecontroleerd.
// 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 non abilitato');
return;
}
// Controlla aggiornamenti ogni 6 ore
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
? 'Nuovo aggiornamento trovato!'
: 'App già aggiornata');
} catch (err) {
console.error('Errore controllo aggiornamento:', err);
}
});
// Ascolta quando una nuova versione e pronta
this.swUpdate.versionUpdates.pipe(
filter((event): event is VersionReadyEvent =>
event.type === 'VERSION_READY'
)
).subscribe(event => {
console.log('Versione corrente:', event.currentVersion.hash);
console.log('Nuova versione:', event.latestVersion.hash);
this.promptUpdate();
});
// Gestisci errori irrecuperabili del SW
this.swUpdate.unrecoverable.subscribe(event => {
console.error('SW in stato irrecuperabile:', event.reason);
// Forza il reload completo
document.location.reload();
});
}
private promptUpdate(): void {
const shouldUpdate = confirm(
'Una nuova versione dell\'app è disponibile. ' +
'Vuoi aggiornare ora?'
);
if (shouldUpdate) {
// Attiva la nuova versione e ricarica
this.swUpdate.activateUpdate().then(() => {
document.location.reload();
});
}
}
}
Levenscyclus van een PWA-update
| Fase | SwUpdate-evenement | Beschrijving |
|---|---|---|
| 1. Detectie | VERSION_DETECTED |
De SW heeft een nieuwe versie op de server gevonden |
| 2. Downloaden | VERSION_INSTALLATION_FAILED |
Fout bij downloaden van nieuwe versie (indien mislukt) |
| 3. Klaar | VERSION_READY |
De nieuwe versie is gedownload en klaar voor activering |
| 4. Activering | activateUpdate() |
De gebruiker bevestigt en de SW activeert de nieuwe versie |
| 5. Opladen | location.reload() |
De pagina wordt opnieuw geladen met bijgewerkte bronnen |
Onherstelbare servicemedewerkerfout
Het evenement unrecoverable geeft aan dat de servicemedewerker dit niet meer kan
de huidige versie van de app correct weergeven (de server is bijvoorbeeld verwijderd
middelen die nog nodig zijn). In dit geval is de enige oplossing het forceren van een herlaadbeurt
compleet met pagina document.location.reload(). Houd deze gebeurtenis in de gaten
in productie om implementatieproblemen te identificeren.
9. Aangepaste installatieprompt
Browsers tonen automatisch een installatiebanner wanneer de PWA voldoet
de installeerbaarheidscriteria. De native banner is echter niet erg zichtbaar en controleerbaar.
Het onderscheppen van de gebeurtenis beforeinstallprompt je kunt er een beleving van creëren
installatie op maat met een speciale knop en een ontwerp dat consistent is met uw 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>Installa la nostra app per un'esperienza migliore!</p>
<button (click)="installApp()">Installa</button>
<button (click)="dismissBanner()">Non ora</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;
// Intercetta l'evento beforeinstallprompt
window.addEventListener('beforeinstallprompt', (event: Event) => {
event.preventDefault();
this.deferredPrompt = event;
this.showInstallBanner.set(true);
});
// Rileva quando l'app viene installata
window.addEventListener('appinstalled', () => {
this.isAppInstalled.set(true);
this.showInstallBanner.set(false);
this.deferredPrompt = null;
console.log('PWA installata con successo!');
});
}
async installApp(): Promise<void> {
if (!this.deferredPrompt) return;
// Mostra il prompt di installazione nativo
this.deferredPrompt.prompt();
const result = await this.deferredPrompt.userChoice;
console.log('Scelta utente:', result.outcome);
if (result.outcome === 'accepted') {
console.log('Installazione accettata');
} else {
console.log('Installazione rifiutata');
}
this.deferredPrompt = null;
this.showInstallBanner.set(false);
}
dismissBanner(): void {
this.showInstallBanner.set(false);
}
private checkIfInstalled(): void {
// Verifica se l'app e già in modalità standalone
const isStandalone = window.matchMedia(
'(display-mode: standalone)'
).matches;
this.isAppInstalled.set(isStandalone);
}
}
PWA-installeerbaarheidscriteria
| Vereiste | Beschrijving |
|---|---|
| HTTPS | App moet worden aangeboden via HTTPS (of localhost voor ontwikkelaar) |
| Webapp-manifest | Moet omvatten name, icons (192px + 512px), start_url, display |
| Dienstverleners | Een actieve servicemedewerker met een gebeurtenishandler fetch |
| Nog niet geïnstalleerd | De app hoeft niet al op uw apparaat te zijn geïnstalleerd |
| Gebruikersinteractie | De gebruiker moet interactie hebben gehad met het domein (klikken, scrollen, enz.) |
10. Testen en foutopsporing
Voor het testen van een PWA zijn specifieke tools nodig omdat de servicemedewerker alleen actief is
in productie en PWA-functies zijn niet beschikbaar tijdens de ontwikkeling met
ng serve. Chrome DevTools en Lighthouse bieden alles wat u nodig heeft
om elk aspect van uw PWA te testen en te debuggen.
Build en lokale service voor testen
# 1. Build di produzione
ng build
# 2. Installa un server HTTP statico (una tantum)
npm install -g http-server
# 3. Servi la build di produzione
cd dist/tua-app/browser
http-server -p 8080 -c-1
# Oppure con npx (senza installazione globale)
npx serve dist/tua-app/browser -l 8080
# 4. Apri il browser su http://localhost:8080
# 5. Apri DevTools > Application tab
Chrome DevTools - Tabblad Applicatie
Het tabblad Sollicitatie van Chrome DevTools is het hoofdpaneel voor inspecteer en debug alle aspecten van de PWA.
DevTools-panelen voor PWA
| Paneel | Functie | Verifiëren |
|---|---|---|
| Manifest | Toon het geparseerde manifest | Naam, pictogrammen, weergavemodus en start-URL zijn correct |
| Dienstverleners | Status van geregistreerde SW | SW actief, updates in behandeling, fouten |
| Cache-opslag | Inhoud cachen | De juiste bronnen bevinden zich in de cache, totale grootte |
| GeïndexeerdeDB | Lokale databases | Offline gegevens zijn succesvol bewaard gebleven |
| Opslag | Opslagquota en gebruik | Gebruikte versus beschikbare ruimte |
Lighthouse PWA-audit
Lighthouse bevat een sectie gewijd aan PWA-validatie. De audit controleert de criteria installeerbaarheid, offline optimalisaties en best practices. Een goed geconfigureerde PWA zou een Lighthouse PWA-score van moeten krijgen 100/100.
Lighthouse PWA-checklist
- De servicemedewerker meldt een behandelaar aan
fetch - De pagina reageert met een code 200, zelfs offline
start_urlin het manifest en offline bereikbaar- Het manifest heeft
nameoshort_name - Het manifest heeft pictogrammen van minimaal 192px en 512px
- Het manifest specificeert
display(standalone/volledig scherm/minimal-ui) - De inhoud heeft de juiste afmetingen voor de viewport
- Metatags
theme-coloraanwezig in HTML - De pagina heeft een
viewportgeldige metatag - De site maakt gebruik van HTTPS
- HTTP wordt omgeleid naar HTTPS
Foutopsporing voor servicemedewerkers
// Nel browser, via DevTools Console:
// 1. Verificare lo stato del service worker
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. Forzare l'aggiornamento del service worker
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => reg.update());
});
// 3. Deregistrare tutti i service worker (per reset completo)
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => reg.unregister());
});
// 4. Ispezionare la cache del service worker
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. Simulare stato offline via DevTools
// Network tab > seleziona "Offline" nel dropdown throttling
// Oppure: Application > Service Workers > spunta "Offline"
Veelvoorkomende problemen en oplossingen
- SW wordt niet bijgewerkt: Gebruik 'Update bij opnieuw laden' in DevTools > Toepassing > Servicemedewerkers tijdens de ontwikkeling
- Aanhoudende oude cache: Volledige opslag wissen via DevTools > Applicatie > Opslag > "Sitegegevens wissen"
- Installatieprompt verschijnt niet: Controleer of aan alle installeerbaarheidscriteria wordt voldaan in het paneel Manifest
- Pushmeldingen komen niet aan: Controleer of de toestemming is "verleend".
Notification.permissionen dat het abonnement geldig is - CORS-cachefouten: Externe bronnen moeten worden aangeboden met de juiste CORS-headers die door de SW in de cache kunnen worden opgeslagen
- SW in "wachtende" staat: De nieuwe SW wacht tot alle app-tabbladen zijn gesloten. VS
skipWaiting()om onmiddellijke activering af te dwingen
Samenvatting en volgende stappen
Progressive Web Apps vertegenwoordigen een natuurlijke evolutie van webapplicaties, het aanbieden van functies die tot een paar jaar geleden exclusief waren voor native apps. Angular maakt het bouwen van PWA’s een stuk eenvoudiger dankzij ingebouwde ondersteuning voor servicemedewerker, intelligente caching, pushmeldingen en beheerde updates.
Sleutelconcepten van dit artikel
- PWA: Installeerbare webapplicaties met offline ondersteuning, pushmeldingen en automatische updates
- Opstelling:
ng add @angular/pwagenereert manifest, SW-configuratie en pictogrammen - Manifest: JSON-bestand dat de app beschrijft (naam, pictogrammen, weergavemodus, start-URL)
- ngsw-config.json: AssetGroups (statische bronnen) en dataGroups (dynamische API's) configureren
- Caching: Prefetch/luie strategieën voor assets, versheid/prestaties voor API-gegevens
- Offline: Detectie van netwerkstatus, IndexedDB voor persistentie, synchronisatie wanneer u weer online bent
- Duw:
SwPushvoor abonnementen en het ontvangen van meldingen, VAPID voor serverauthenticatie - Updates:
SwUpdateom nieuwe releases te controleren, te melden en te activeren - Installatieprompt:
beforeinstallpromptvoor aangepaste installatie-UI - Testen: Chrome DevTools-tabblad Applicatie, Lighthouse PWA-audit, SW-foutopsporing
In het volgende artikel in de serie zullen we de Geavanceerde afhankelijkheidsinjectie
in Angular, analyse van aangepaste injectietokens, boomschudbare providers,
multi-provider, fabrieksprovider en scopingstrategieën met providedIn.







