Angular + PWA: creați o aplicație instalabilă
Le Aplicații web progresive (PWA) ele reprezintă puntea dintre aplicațiile web și aplicații native. Datorită tehnologiilor precum i Lucrătorii de servicii, The Manifestul aplicației web iar cel Notificare push, oferă un PWA experiențe comparabile cu cele ale unei aplicații instalate din magazin, dar fără costurile și complexitatea distribuţiei native. Angular integrează suportul PWA în mod nativ, făcând transformarea unei aplicații web într-o aplicație instalabilă un proces simplu si bine documentat.
În acest al optulea articol al seriei Angular modern vom explora fiecare aspect a creării unui PWA cu Angular: de la configurația inițială la managementul avansat cache, de la suport offline la notificări push și mecanisme de actualizare prompt de instalare automată și personalizată. Vom vedea cum să testăm și să depanăm fiecare funcționalitate cu instrumentele Chrome DevTools.
Ce veți învăța în acest articol
- Ce sunt PWA și ce avantaje oferă acestea față de aplicațiile native
- Cum se configurează Angular PWA cu
ng add @angular/pwa - Personalizarea manifestului aplicației web pentru pictograme, culori și moduri de afișare
- Configurarea Service Worker cu
ngsw-config.json - Strategii de stocare în cache: prefatch, lazy, network-first, cache-first
- Suport offline cu pagini alternative și IndexedDB
- Notificare push cu serviciul
SwPush - Gestionați actualizările cu
SwUpdate - Instalați promptul personalizat cu
beforeinstallprompt - Testarea și depanarea cu Chrome DevTools și Lighthouse
Prezentare generală a seriei unghiulare moderne
| # | Articol | Concentrează-te |
|---|---|---|
| 1 | Semnale unghiulare | Reactivitate fină |
| 2 | Detectarea schimbărilor fără zonă | Ștergeți Zone.js |
| 3 | Șabloane noi @if, @for, @defer | Flux de control modern |
| 4 | Componente de sine stătătoare | Arhitectură fără NgModule |
| 5 | Forme de semnal | Formulare receptive cu semnale |
| 6 | SSR și hidratare incrementală | Redare pe partea serverului |
| 7 | Core Web Vitals în Angular | Performanță și valori |
| 8 | Sunteți aici - Angular PWA | Aplicații web progresive |
| 9 | Injecție avansată de dependență | DI tree-shaking |
| 10 | Migrați de la Angular 17 la 21 | Ghid de migrare |
1. Ce sunt aplicațiile web progresive
Una Aplicații web progresive este o aplicație web care utilizează tehnologii modern pentru a oferi utilizatorului o experiență comparabilă cu cea a unei aplicații native. Termenul „progresiv” înseamnă că aplicația funcționează pentru fiecare utilizator, indiferent de browser utilizate, îmbunătățind progresiv experiența pe browserele care acceptă caracteristici avansate.
Caracteristicile cheie ale PWA
PWA-urile se disting de aplicațiile web tradiționale printr-o serie de capabilități care le apropie de aplicațiile native distribuite prin magazine.
PWA vs Native App vs Traditional Web App
| Caracteristică | Aplicația web | PWA | Aplicația nativă |
|---|---|---|---|
| Instalabil | No | Si | Si |
| Funcționează offline | No | Si | Si |
| Notificare push | No | Si | Si |
| Actualizare automată | Si | Si | Prin magazin |
| Acces la hardware | Limitat | Bun | Complet |
| Distributie | URL | URL + Instalare | App Store |
| Costul de dezvoltare | Bas | Bas | Ridicat |
| SEO | Si | Si | Nu (indexare magazin) |
Când să alegeți un PWA
PWA sunt alegerea ideală atunci când doriți să ajungeți la cel mai mare număr de utilizatori o singură bază de cod. Sunt perfecte pentru comerțul electronic, bloguri, tablouri de bord ale companiei, instrumente productivitate și orice aplicație care beneficiază de acces offline și notificări împinge. Dacă aplicația necesită acces profund la hardware (Bluetooth avansat, NFC, senzori specificații), poate fi în continuare necesară o aplicație nativă.
2. Configurați Angular PWA
Angular oferă o schemă oficială care automatizează configurarea PWA. Cu a o singură comandă, CLI generează toate fișierele necesare: manifestul, configurația lucrător de service și pictograme implicite.
# 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)
Configurare în app.config.ts
După rularea schemei, app.config.ts este actualizat
automat pentru a înregistra lucrătorul de service. Înregistrarea are loc numai în
producţie şi după aplicare stabilizare.
// 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'
})
]
};
Lucrător de servicii și mediu de dezvoltare
Lucrătorul de servicii este dezactivat în modul de dezvoltare (isDevMode()) de ce
memorarea agresivă în cache ar interfera cu reîncărcarea la cald și reîncărcarea live. Pentru a testa
lucrătorul de service la nivel local, trebuie să rulați o versiune de producție cu
ng build și serviți fișierele statice cu un server HTTP ca
http-server o npx serve.
3. Manifestul aplicației web
Il Manifestul aplicației web este un fișier JSON care descrie aplicația dvs browser și sistem de operare. Conține informații precum numele aplicației, pictograme, culorile temei și modul de afișare. Fără un manifest valid, browserul nu oferă posibilitatea de a instala aplicația.
{
"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" }]
}
]
}
Modul de afișare a manifestului
| Modul | Descriere | Caz de utilizare |
|---|---|---|
standalone |
Aplicația apare ca o aplicație nativă fără bară de adrese | Cea mai comună alegere pentru PWA |
fullscreen |
Aplicația ocupă întregul ecran, fără interfață de utilizare a browserului | Jocuri, prezentări captivante |
minimal-ui |
Similar cu standalone, dar cu comenzi minime de navigare | Aplicații care necesită navigare înapoi/înainte |
browser |
Aplicația se deschide într-o filă standard de browser | Site-uri web tradiționale (nu sunt recomandate pentru PWA) |
4. Configurarea Service Worker
Dosarul ngsw-config.json este inima configurației PWA în Angular.
Definește ce resurse sunt stocate în cache, cum sunt actualizate și cu care
strategie. Angular generează automat fișierul ngsw-worker.js în timpul
construcția de producție bazată pe această configurație.
{
"$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
| Proprietate | assetGroups | dateGroups |
|---|---|---|
| Domeniul de aplicare | Resurse pentru aplicații statice (JS, CSS, imagini) | Date dinamice din API |
| Versiune | Pe baza hash-ului fișierului | Bazat pe timp (maxAge) |
| installMode | prefetch o lazy |
N / A |
| strategie | N / A | freshness o performance |
| Actualizare | Când versiunea aplicației se schimbă | Bazat pe maxAge și timeout |
5. Strategii de stocare în cache
Alegerea strategiei de stocare în cache este esențială pentru a echilibra performanța și
prospețimea datelor. Angular NGSW acceptă două strategii principale pentru i
dataGroups: prospeţime (în primul rând în rețea) e
performanţă (cache-first), precum și stocarea în cache statică pentru active.
Strategii de stocare în cache în detaliu
| Strategie | Prioritate | Comportament | Caz de utilizare |
|---|---|---|---|
| preluare (assetGroups) | Cache | Descărcați toate resursele imediat după instalarea software-ului | Aplicații Shell, JS și CSS de bază |
| leneş (assetGroups) | Memorarea în cache la cerere | Memorează în cache resursa numai atunci când este solicitat | Imagini, fonturi, active secundare |
| prospeţime (grupuri de date) | Reţea | Încercați rețeaua; dacă timeout a expirat, utilizați memoria cache | Profil utilizator, notificări, date în timp real |
| performanţă (grupuri de date) | Cache | Utilizați memoria cache dacă este disponibilă și nu a expirat; altfel reţea | Articole de blog, catalog de produse, date statice |
Strategia prospeţime este echivalent cu modelul în primul rând în rețea: lucrătorul de service încearcă mai întâi rețeaua și, dacă cererea eșuează în timpul expirării specificat, este necesară versiunea în cache. Strategia performanţă este în schimb cache-în primul rând: aveți nevoie imediat de versiunea cache (dacă nu a expirat) și reîmprospătați memoria cache în fundal.
Stale-While-Revalidate cu Angular
Angular NGSW nu suportă nativ strategia învechit-în timp ce-revalidează,
dar îl poți simula combinând strategia performanţă cu a
maxAge scurt. De exemplu, setarea "maxAge": "5m" cache-ul
va servi imediat datele, dar după 5 minute va face o nouă solicitare către rețea
la următoarea conectare. Dacă aveți nevoie de un control mai granular, luați în considerare utilizarea acestuia
direct de Cache API cu un lucrător de servicii personalizate.
6. Asistență offline
Una dintre cele mai importante caracteristici ale unui PWA este capacitatea de a lucra fără conexiune la internet. Angular gestionează automat stocarea în cache a resurselor static prin intermediul lucrătorului de service, dar pentru o experiență completă offline, acest lucru este necesar de asemenea, gestionați starea conexiunii, paginile de rezervă și persistența serverului date locale.
Detectarea stării conexiunii
// 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
}
}
Persistența datelor cu IndexedDB
Pentru a gestiona datele structurate offline, IndexedDB este cea mai robustă soluție.
Spre deosebire de localStorage (sincron, limită 5-10MB), suportă IndexedDB
volume mari de date, interogări indexate și operații asincrone.
// 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);
});
}
}
Limitări ale stocării offline
- Partajare browser: Browserele limitează spațiul de stocare disponibil (de obicei 50-80% din spațiul pe disc). STATELE UNITE ALE AMERICII
navigator.storage.estimate()pentru a verifica spațiul disponibil - Politica de evacuare: Dacă spațiul este insuficient, browserul poate șterge datele. Solicitați stocare persistentă cu
navigator.storage.persist() - Siguranța SSR: IndexedDB nu există pe partea serverului. Utilizați întotdeauna
isPlatformBrowserînainte de a-l accesa - Conflicte de sincronizare: Când utilizatorul revine online, datele locale pot intra în conflict cu datele serverului. Implementați o strategie de soluționare a conflictelor
7. Notificare push
Notificările push permit PWA să trimită mesaje utilizatorului chiar și atunci când
aplicația nu este activă. Angular oferă serviciul SwPush care simplifică
managing your subscriptions and receiving notifications via the
Protocolul Web Push.
Abonament la notificări push
// 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);
}
}
}
Trimiterea notificărilor de pe 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);
}
}
}
Chei VAID
Cheile VAID (Identificarea voluntară a serverului de aplicații) sunt folosite pentru
autentificați-vă serverul la serviciul push de browser. Le puteți genera cu ajutorul
comanda npx web-push generate-vapid-keys. Trebuie introdusă cheia publică
în frontend, cel privat rămâne exclusiv pe server. Nu comite niciodată
cheie privată în depozit.
8. Gestionarea actualizărilor
Când lansați o nouă versiune a PWA, lucrătorul de service trebuie să descarce
resurse actualizate și anunță utilizatorul. Angular oferă serviciul
SwUpdate pentru a gestiona într-un fel ciclul de viață al actualizării
verificat.
// 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();
});
}
}
}
Ciclul de viață al unei actualizări PWA
| Fază | eveniment SwUpdate | Descriere |
|---|---|---|
| 1. Detectare | VERSION_DETECTED |
SW a găsit o nouă versiune pe server |
| 2. Descărcări | VERSION_INSTALLATION_FAILED |
Eroare la descărcarea versiunii noi (dacă a eșuat) |
| 3. Gata | VERSION_READY |
Noua versiune este descărcată și gata pentru activare |
| 4. Activare | activateUpdate() |
Utilizatorul confirmă și SW activează noua versiune |
| 5. Reîncărcare | location.reload() |
Pagina se reîncarcă cu resurse actualizate |
Eroare de serviciu irecuperabilă
Evenimentul unrecoverable indică faptul că lucrătorul de serviciu nu mai poate
difuzează corect versiunea curentă a aplicației (de exemplu, serverul a eliminat
resursele încă necesare). În acest caz, singura soluție este forțarea reîncărcării
complet cu pagina document.location.reload(). Monitorizați acest eveniment
în producție pentru a identifica problemele de implementare.
9. Solicitare de instalare personalizată
Browserele afișează automat un banner de instalare atunci când PWA satisface
criteriile de instalabilitate. Totuși, bannerul nativ nu este foarte vizibil și controlabil.
Interceptarea evenimentului beforeinstallprompt poți crea o experiență de
instalare personalizată cu un buton dedicat și un design compatibil cu aplicația dvs.
// 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);
}
}
Criterii de instalare PWA
| Cerinţă | Descriere |
|---|---|
| HTTPS | Aplicația trebuie să fie difuzată prin HTTPS (sau localhost pentru dezvoltatori) |
| Manifestul aplicației web | Trebuie să includă name, icons (192px + 512px), start_url, display |
| Lucrătorii de servicii | Un lucrător de serviciu activ cu un handler de evenimente fetch |
| Nu este deja instalat | Aplicația nu trebuie să fie deja instalată pe dispozitivul dvs |
| Interacțiunea utilizatorului | Utilizatorul trebuie să fi interacționat cu domeniul (clic, derulați etc.) |
10. Testare și depanare
Testarea unui PWA necesită instrumente specifice, deoarece lucrătorul de service este doar activ
în producție și caracteristicile PWA nu sunt disponibile în timpul dezvoltării cu
ng serve. Chrome DevTools și Lighthouse vă oferă tot ce aveți nevoie
pentru a testa și depana fiecare aspect al PWA.
Construire și serviciu local pentru testare
# 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 - Fila Aplicație
Fila Aplicație de Chrome DevTools este panoul principal pentru inspectați și depanați toate aspectele PWA.
Panouri DevTools pentru PWA
| Panou | Funcţie | Verifica |
|---|---|---|
| Manifesta | Afișați manifestul analizat | Numele, pictogramele, modul de afișare, adresa URL de pornire sunt corecte |
| Lucrătorii de servicii | Starea SW înregistrată | SW activ, actualizări în așteptare, erori |
| Stocare în cache | Conținutul din cache | Resursele potrivite sunt în cache, dimensiune totală |
| IndexedDB | Baze de date locale | Datele offline au persistat cu succes |
| Depozitare | Cota de stocare și utilizare | Spațiu folosit vs spațiu disponibil |
Audit PWA al farului
Lighthouse include o secțiune dedicată validării PWA. Auditul verifică criteriile instalabilitate, optimizări offline și bune practici. Un PWA bine configurat ar trebui să obțină un scor PWA Lighthouse de 100/100.
Lista de verificare a farului PWA
- Lucrătorul de service înregistrează un handler
fetch - Pagina răspunde cu un cod 200 chiar și offline
start_urlîn manifest și accesibil offline- Manifestul are
nameoshort_name - Manifestul are pictograme de cel puțin 192 px și 512 px
- Manifestul specifică
display(autonom/ecran complet/minimal-ui) - Conținutul este dimensionat corespunzător pentru fereastra de vizualizare
- Meta-etichete
theme-colorprezent în HTML - Pagina are un
viewportmetaetichetă validă - Site-ul folosește HTTPS
- HTTP este redirecționat către HTTPS
Depanarea lucrătorului de service
// 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"
Probleme și soluții comune
- SW nu se actualizează: Utilizați „Actualizare la reîncărcare” în DevTools > Application > Service Workers în timpul dezvoltării
- Cache vechi persistent: Ștergeți spațiul de stocare complet din DevTools > Aplicație > Stocare > „Ștergeți datele site-ului”
- Solicitarea de instalare nu apare: Verificați dacă toate criteriile de instalare sunt îndeplinite în panoul Manifest
- Notificările push nu sosesc: Verificați dacă permisiunea este „acordată” cu
Notification.permissionși că abonamentul este valabil - Erori de cache CORS: Resursele externe trebuie să fie furnizate cu anteturi CORS adecvate pentru a fi stocate în cache de către SW
- SW în starea „în așteptare”: Noul SW așteaptă ca toate filele aplicației să fie închise. STATELE UNITE ALE AMERICII
skipWaiting()pentru a forța activarea imediată
Rezumat și pașii următori
Progressive Web Apps reprezintă o evoluție naturală a aplicațiilor web, oferind funcții care până acum câțiva ani erau exclusive pentru aplicațiile native. Angular face construirea PWA mult mai ușoară datorită suportului încorporat pentru lucrător de service, cache inteligentă, notificare push și actualizări gestionate.
Concepte cheie ale acestui articol
- PWA: Aplicații web instalabile cu suport offline, notificări push și actualizări automate
- Înființat:
ng add @angular/pwagenerează manifest, configurație SW și pictograme - Manifesta: Fișier JSON care descrie aplicația (nume, pictograme, modul de afișare, URL de pornire)
- ngsw-config.json: Configurarea assetGroups (resurse statice) și dataGroups (API-uri dinamice)
- Memorarea în cache: Strategii de preluare anticipată/lenenă pentru active, prospețime/performanță pentru datele API
- Offline: Detectarea stării rețelei, IndexedDB pentru persistență, sincronizare la revenirea online
- Apăsaţi:
SwPushpentru abonamente și primirea notificărilor, VAPID pentru autentificarea serverului - Actualizări:
SwUpdatepentru a verifica, notifica și activa versiuni noi - prompt de instalare:
beforeinstallpromptpentru interfața de utilizare personalizată - Testare: Fila Aplicație Chrome DevTools, audit Lighthouse PWA, depanare SW
În următorul articol din serie vom explora Injecție avansată de dependență
în Angular, analizând jetoane de injecție personalizate, furnizori care se pot agita în arbori,
multi-furnizor, furnizor de fabrică și strategii de scoping cu providedIn.







