Angular + PWA: utwórz aplikację do zainstalowania
Le Progresywne aplikacje internetowe (PWA) stanowią pomost pomiędzy aplikacjami internetowymi i aplikacje natywne. Dzięki technologiom takim jak m.in Pracownicy usług, il Manifest aplikacji internetowej e le Powiadomienie push, oferuje PWA doświadczenia porównywalne z aplikacjami zainstalowanymi ze sklepu, ale bez kosztów i złożoność natywnej dystrybucji. Angular natywnie integruje obsługę PWA, dzięki czemu przekształcanie aplikacji internetowej w aplikację instalowaną staje się procesem proste i dobrze udokumentowane.
W ósmym artykule z tej serii Nowoczesny kątowy zbadamy każdy aspekt tworzenia PWA z Angularem: od wstępnej konfiguracji po zaawansowane zarządzanie cache, od wsparcia offline po powiadomienia push i mechanizmy aktualizacji monit o automatyczną i niestandardową instalację. Przyjrzymy się, jak testować i debugować każdy z nich funkcjonalności z narzędziami Chrome DevTools.
Czego dowiesz się w tym artykule
- Czym są PWA i jaką przewagę mają nad aplikacjami natywnymi?
- Jak skonfigurować Angular PWA za pomocą
ng add @angular/pwa - Dostosowywanie manifestu aplikacji internetowej pod kątem ikon, kolorów i trybów wyświetlania
- Konfigurowanie Service Workera za pomocą
ngsw-config.json - Strategie buforowania: pobieranie z wyprzedzeniem, leniwe, najpierw sieć, najpierw pamięć podręczna
- Obsługa offline ze stronami rezerwowymi i IndexedDB
- Powiadomienia push w ramach usługi
SwPush - Zarządzaj aktualizacjami za pomocą
SwUpdate - Zainstaluj niestandardowy monit za pomocą
beforeinstallprompt - Testowanie i debugowanie za pomocą Chrome DevTools i Lighthouse
Przegląd nowoczesnej serii Angular
| # | Przedmiot | Centrum |
|---|---|---|
| 1 | Sygnały kątowe | Precyzyjna reakcja |
| 2 | Bezstrefowe wykrywanie zmian | Usuń Zone.js |
| 3 | Nowe szablony @if, @for, @defer | Nowoczesny przepływ sterowania |
| 4 | Samodzielne komponenty | Architektura bez NgModule |
| 5 | Formy sygnałowe | Responsywne formularze z sygnałami |
| 6 | SSR i przyrostowe nawodnienie | Renderowanie po stronie serwera |
| 7 | Podstawowe wskaźniki internetowe w Angular | Wydajność i metryki |
| 8 | Jesteś tutaj - Angular PWA | Progresywne aplikacje internetowe |
| 9 | Zaawansowane wstrzykiwanie zależności | DI drzewiaste |
| 10 | Migracja z Angular 17 do 21 | Przewodnik po migracji |
1. Czym są progresywne aplikacje internetowe
Una Progresywne aplikacje internetowe to aplikacja internetowa wykorzystująca technologie nowoczesny, aby zapewnić użytkownikowi doświadczenie porównywalne z aplikacją natywną. Termin „progresywny” oznacza, że aplikacja działa dla każdego użytkownika, niezależnie od przeglądarki używane, stopniowo poprawiając działanie przeglądarek obsługujących zaawansowane funkcje.
Kluczowe cechy PWA
Aplikacje PWA odróżniają się od tradycyjnych aplikacji internetowych szeregiem funkcji, które umożliwiają przybliżają je do aplikacji natywnych dystrybuowanych za pośrednictwem sklepów.
PWA vs aplikacja natywna vs tradycyjna aplikacja internetowa
| Charakterystyczny | Aplikacja internetowa | PWA | Aplikacja natywna |
|---|---|---|---|
| Możliwość zainstalowania | No | Si | Si |
| Działa w trybie offline | No | Si | Si |
| Powiadomienie push | No | Si | Si |
| Automatyczna aktualizacja | Si | Si | Przez sklep |
| Dostęp do sprzętu | Ograniczony | Dobry | Kompletny |
| Dystrybucja | Adres URL | Adres URL + instalacja | Sklep z aplikacjami |
| Koszt rozwoju | Bas | Bas | Wysoki |
| SEO | Si | Si | Nie (indeksowanie sklepu) |
Kiedy wybrać PWA
PWA są idealnym wyborem, gdy zależy Ci na dotarciu do jak największej liczby użytkowników pojedyncza baza kodu. Świetnie sprawdzają się w e-commerce, blogach, dashboardach firmowych, narzędziach produktywność i dowolną aplikację korzystającą z dostępu w trybie offline i powiadomień pchnij. Jeśli aplikacja wymaga głębokiego dostępu do sprzętu (zaawansowany Bluetooth, NFC, czujniki szczegółowe), aplikacja natywna może być nadal konieczna.
2. Skonfiguruj Angular PWA
Angular udostępnia oficjalny schemat, który automatyzuje konfigurację PWA. Z pojedyncze polecenie, CLI generuje wszystkie niezbędne pliki: manifest, konfigurację Service Worker i domyślne ikony.
# 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)
Konfiguracja w app.config.ts
Po uruchomieniu schematu plik app.config.ts jest aktualizowany
automatycznie zarejestrować pracownika serwisu. Rejestracja odbywa się wyłącznie w
stabilizacja produkcji i po zastosowaniu.
// 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'
})
]
};
Pracownik usług i środowisko programistyczne
Worker usługi jest wyłączony w trybie programistycznym (isDevMode()) dlaczego
agresywne buforowanie zakłócałoby przeładowywanie na gorąco i przeładowywanie na żywo. Aby przetestować
Service Worker lokalnie, musisz uruchomić kompilację produkcyjną
ng build i udostępniaj pliki statyczne za pomocą serwera HTTP, np
http-server o npx serve.
3. Manifest aplikacji internetowej
Il Manifest aplikacji internetowej to plik JSON opisujący Twoją aplikację przeglądarka i system operacyjny. Zawiera informacje, takie jak nazwa aplikacji, ikony, kolory motywu i tryb wyświetlania. Bez prawidłowego manifestu, przeglądarka nie oferuje możliwości zainstalowania aplikacji.
{
"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" }]
}
]
}
Tryb wyświetlania manifestu
| Tryb | Opis | Przypadek użycia |
|---|---|---|
standalone |
Aplikacja pojawia się jako aplikacja natywna, bez paska adresu | Najczęstszy wybór dla PWA |
fullscreen |
Aplikacja zajmuje cały ekran, bez interfejsu przeglądarki | Gry, wciągające prezentacje |
minimal-ui |
Podobny do samodzielnego, ale z minimalnymi elementami sterującymi nawigacją | Aplikacje wymagające nawigacji wstecz/do przodu |
browser |
Aplikacja otwiera się w standardowej karcie przeglądarki | Tradycyjne strony internetowe (niezalecane dla PWA) |
4. Konfiguracja Service Workera
Plik ngsw-config.json jest sercem konfiguracji PWA w Angular.
Określa, które zasoby są buforowane, w jaki sposób są aktualizowane i za pomocą czego
strategia. Angular automatycznie generuje plik ngsw-worker.js podczas
kompilacja produkcyjna oparta na tej konfiguracji.
{
"$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": [
"/**",
"!/__**"
]
}
Grupy aktywów a grupy danych
| Nieruchomość | Grupy aktywów | Grupy danych |
|---|---|---|
| Zakres | Zasoby aplikacji statycznych (JS, CSS, obrazy) | Dane dynamiczne z API |
| Wersjonowanie | Na podstawie skrótu pliku | Oparte na czasie (maxAge) |
| tryb instalacji | prefetch o lazy |
Nie dotyczy |
| strategia | Nie dotyczy | freshness o performance |
| Aktualizacja | Gdy zmieni się wersja aplikacji | Na podstawie maksymalnego wieku i limitu czasu |
5. Strategie buforowania
Wybór strategii buforowania jest niezbędny do zrównoważenia wydajności i
świeżość danych. Angular NGSW obsługuje dwie główne strategie dla m.in
dataGroups: świeżość (najpierw sieć) e
wydajność (najpierw pamięć podręczna), a także statyczne buforowanie zasobów.
Szczegółowe strategie buforowania
| Strategia | Priorytet | Zachowanie | Przypadek użycia |
|---|---|---|---|
| pobierz z wyprzedzeniem (grupy zasobów) | Kryjówka | Pobierz wszystkie zasoby natychmiast po zainstalowaniu oprogramowania | Aplikacje powłoki, rdzeń JS i CSS |
| leniwy (grupy zasobów) | Buforowanie na żądanie | Buforuje zasób tylko na żądanie | Obrazy, czcionki, zasoby wtórne |
| świeżość (Grupy danych) | Sieć | Wypróbuj sieć; jeśli upłynął limit czasu, użyj pamięci podręcznej | Profil użytkownika, powiadomienia, dane w czasie rzeczywistym |
| wydajność (Grupy danych) | Kryjówka | Użyj pamięci podręcznej, jeśli jest dostępna i nie wygasła; inaczej sieć | Artykuły na blogu, katalog produktów, dane statyczne |
Strategia świeżość jest równoważne wzorowi najpierw sieć: pracownik usługi najpierw próbuje połączyć się z siecią, a jeśli żądanie nie powiedzie się, w ramach limitu czasu określony, wymagana jest wersja buforowana. Strategia wydajność to jest zamiast tego najpierw pamięć podręczna: potrzebujesz natychmiastowej wersji w pamięci podręcznej (jeśli nie wygasła) i odśwież pamięć podręczną w tle.
Nieaktualne-while-Revalidate za pomocą Angular
Angular NGSW nie obsługuje natywnie tej strategii nieaktualne podczas ponownej weryfikacji,
ale możesz to symulować, łącząc strategię wydajność con un
maxAge krótki. Na przykład ustawienie "maxAge": "5m" pamięć podręczna
natychmiast prześle dane, ale po 5 minutach złoży nowe żądanie do sieci
przy następnym logowaniu. Jeśli potrzebujesz bardziej szczegółowej kontroli, rozważ jej użycie
bezpośrednio z Pamięć podręczna API z pracownikiem serwisu niestandardowego.
6. Wsparcie offline
Jedną z najważniejszych cech PWA jest możliwość pracy bez niego połączenie internetowe. Angular automatycznie obsługuje buforowanie zasobów statyczny za pośrednictwem Service Workera, ale aby móc w pełni korzystać z trybu offline, jest to konieczne zarządzaj także stanem połączenia, stronami awaryjnymi i trwałością serwera dane lokalne.
Wykrywanie stanu połączenia
// 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
}
}
Trwałość danych w IndexedDB
Aby zarządzać danymi strukturalnymi w trybie offline, IndexedDB jest to najsolidniejsze rozwiązanie.
Inaczej localStorage (synchroniczny, limit 5-10 MB), obsługuje IndexedDB
duże wolumeny danych, indeksowane zapytania i operacje asynchroniczne.
// 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);
});
}
}
Ograniczenia przechowywania offline
- Udział przeglądarki: Przeglądarki ograniczają dostępną pamięć (zwykle 50-80% miejsca na dysku). USA
navigator.storage.estimate()aby sprawdzić dostępną przestrzeń - Polityka eksmisji: W przypadku braku miejsca przeglądarka może usunąć dane. Poproś o trwałe miejsce do przechowywania za pomocą
navigator.storage.persist() - Bezpieczeństwo SSR: IndexedDB nie istnieje po stronie serwera. Zawsze używaj
isPlatformBrowserprzed uzyskaniem do niego dostępu - Konflikty synchronizacji: Gdy użytkownik wróci do trybu online, dane lokalne mogą powodować konflikt z danymi serwera. Wdrożyć strategię rozwiązywania konfliktów
7. Powiadomienie push
Powiadomienia push umożliwiają Twojemu PWA wysyłanie wiadomości do użytkownika nawet wtedy, gdy jest to możliwe
aplikacja nie jest aktywna. Angular zapewnia usługę SwPush co upraszcza
zarządzanie swoimi subskrypcjami i otrzymywanie powiadomień za pośrednictwem
Protokół Web Push.
Subskrypcja powiadomień 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);
}
}
}
Wysyłanie powiadomień z serwera
// 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);
}
}
}
Klucze VAPID
Klucze VAPID (Dobrowolna identyfikacja serwera aplikacji) służą do
uwierzytelnij swój serwer w usłudze push przeglądarki. Można je wygenerować za pomocą
polecenie npx web-push generate-vapid-keys. Należy wprowadzić klucz publiczny
w frontendzie prywatny pozostaje wyłącznie na serwerze. Nigdy nie popełniaj
klucz prywatny w repozytorium.
8. Zarządzanie aktualizacjami
Po wydaniu nowej wersji programu PWA pracownik serwisu musi pobrać plik
zaktualizowane zasoby i powiadomić użytkownika. Angular zapewnia usługę
SwUpdate aby w jakiś sposób zarządzać cyklem życia aktualizacji
sprawdzone.
// 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();
});
}
}
}
Cykl życia aktualizacji PWA
| Faza | Wydarzenie SwUpdate | Opis |
|---|---|---|
| 1. Wykrywanie | VERSION_DETECTED |
Oprogramowanie znalazło nową wersję na serwerze |
| 2. Pliki do pobrania | VERSION_INSTALLATION_FAILED |
Błąd podczas pobierania nowej wersji (jeśli nie powiodło się) |
| 3. Gotowe | VERSION_READY |
Nowa wersja została pobrana i gotowa do aktywacji |
| 4. Aktywacja | activateUpdate() |
Użytkownik potwierdza, a oprogramowanie aktywuje nową wersję |
| 5. Naładuj | location.reload() |
Strona ładuje się ponownie ze zaktualizowanymi zasobami |
Nieodwracalny błąd pracownika serwisu
Wydarzenie unrecoverable wskazuje, że pracownik serwisu nie jest już w stanie
poprawnie wyświetlają aktualną wersję aplikacji (na przykład serwer usunął
zasoby nadal potrzebne). W takim przypadku jedynym rozwiązaniem jest wymuszenie przeładowania
w komplecie ze stroną document.location.reload(). Monitoruj to wydarzenie
w środowisku produkcyjnym, aby zidentyfikować problemy z wdrażaniem.
9. Monit dotyczący instalacji niestandardowej
Przeglądarki automatycznie wyświetlają baner instalacyjny, gdy PWA spełnia wymagania
kryteria instalowalności. Jednak natywny baner nie jest zbyt widoczny i łatwy do kontrolowania.
Przechwycenie wydarzenia beforeinstallprompt możesz stworzyć doświadczenie
spersonalizowana instalacja z dedykowanym przyciskiem i designem spójnym z Twoją aplikacją.
// 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);
}
}
Kryteria instalacyjne PWA
| Wymóg | Opis |
|---|---|
| HTTPS | Aplikacja musi być udostępniana przez HTTPS (lub localhost w przypadku deweloperów) |
| Manifest aplikacji internetowej | Musi zawierać name, icons (192px + 512px), start_url, display |
| Pracownicy usług | Aktywny proces roboczy usługi z procedurą obsługi zdarzeń fetch |
| Jeszcze nie zainstalowany | Aplikacja nie musi być już zainstalowana na Twoim urządzeniu |
| Interakcja użytkownika | Użytkownik musiał wejść w interakcję z domeną (kliknąć, przewinąć itp.) |
10. Testowanie i debugowanie
Testowanie PWA wymaga specyficznych narzędzi, ponieważ service worker jest tylko aktywny
w wersji produkcyjnej i funkcje PWA nie są dostępne podczas programowania
ng serve. Chrome DevTools i Lighthouse zapewniają wszystko, czego potrzebujesz
do testowania i debugowania każdego aspektu Twojego PWA.
Kompilacja i usługa lokalna do testowania
# 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 – karta Aplikacja
Zakładka Aplikacja Chrome DevTools to główny panel dla sprawdzaj i debuguj wszystkie aspekty PWA.
Panele DevTools dla PWA
| Płyta | Funkcjonować | Zweryfikować |
|---|---|---|
| Oczywisty | Pokaż przeanalizowany manifest | Nazwa, ikony, tryb wyświetlania, początkowy adres URL są prawidłowe |
| Pracownicy usług | Status zarejestrowanego SW | Oprogramowanie aktywne, aktualizacje oczekujące, błędy |
| Pamięć podręczna | Zawartość pamięci podręcznej | Odpowiednie zasoby znajdują się w pamięci podręcznej, całkowity rozmiar |
| Indeksowana baza danych | Lokalne bazy danych | Dane offline zostały pomyślnie utrwalone |
| Składowanie | Limit miejsca i wykorzystanie | Powierzchnia wykorzystana vs dostępna |
Audyt Lighthouse PWA
Lighthouse zawiera sekcję poświęconą walidacji PWA. Audyt sprawdza kryteria łatwość instalacji, optymalizacje offline i najlepsze praktyki. Dobrze skonfigurowane PWA powinien uzyskać wynik Lighthouse PWA na poziomie 100/100.
Lista kontrolna Lighthouse PWA
- Pracownik serwisu rejestruje handlera
fetch - Strona odpowiada kodem 200 nawet w trybie offline
start_urlw manifeście i można go uzyskać offline- Manifest ma
nameoshort_name - Manifest zawiera ikony o rozmiarach co najmniej 192 i 512 pikseli
- Manifest określa
display(samodzielny/pełny ekran/minimalny interfejs użytkownika) - Rozmiar treści jest odpowiednio dopasowany do widocznego obszaru
- Metatagi
theme-colorobecny w HTML - Na stronie znajduje się
viewportprawidłowy metatag - Strona korzysta z protokołu HTTPS
- HTTP jest przekierowywany do HTTPS
Debugowanie pracownika serwisu
// 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"
Typowe problemy i rozwiązania
- Oprogramowanie nie aktualizuje się: Użyj opcji „Aktualizuj przy ponownym załadowaniu” w DevTools > Aplikacja > Service Workers podczas programowania
- Trwała stara pamięć podręczna: Wyczyść całą pamięć w DevTools > Aplikacja > Pamięć > „Wyczyść dane witryny”
- Monit o instalację nie pojawia się: Sprawdź, czy w panelu Manifest zostały spełnione wszystkie kryteria instalacji
- Powiadomienia push nie docierają: Sprawdź, czy pozwolenie zostało „udzielone” za pomocą
Notification.permissioni że subskrypcja jest ważna - Błędy pamięci podręcznej CORS: Zasoby zewnętrzne muszą być obsługiwane z odpowiednimi nagłówkami CORS, aby mogły być buforowane przez oprogramowanie komputerowe
- SW w stanie „oczekiwania”: Nowe oprogramowanie czeka na zamknięcie wszystkich kart aplikacji. USA
skipWaiting()aby wymusić natychmiastową aktywację
Podsumowanie i kolejne kroki
Progressive Web Apps stanowią naturalną ewolucję aplikacji internetowych, oferując funkcje, które jeszcze kilka lat temu były dostępne wyłącznie w aplikacjach natywnych. Angular znacznie ułatwia tworzenie PWA dzięki wbudowanej obsłudze service worker, inteligentne buforowanie, powiadomienia push i zarządzane aktualizacje.
Kluczowe pojęcia tego artykułu
- PWA: Instalowalne aplikacje internetowe z obsługą offline, powiadomieniami push i automatycznymi aktualizacjami
- Organizować coś:
ng add @angular/pwageneruje manifest, konfigurację oprogramowania i ikony - Oczywisty: Plik JSON opisujący aplikację (nazwa, ikony, tryb wyświetlania, początkowy adres URL)
- ngsw-config.json: Konfigurowanie grup aktywów (zasobów statycznych) i grup danych (dynamicznych interfejsów API)
- Buforowanie: Strategie pobierania wstępnego/leniwego dla zasobów, świeżość/wydajność danych API
- Nieaktywny: Wykrywanie stanu sieci, IndexedDB dla trwałości, synchronizacja po powrocie do trybu online
- Naciskać:
SwPushdla subskrypcji i otrzymywania powiadomień, VAPID dla uwierzytelnienia serwera - Aktualizacje:
SwUpdateaby sprawdzać, powiadamiać i aktywować nowe wydania - Monit instalacji:
beforeinstallpromptdla niestandardowego interfejsu konfiguracji - Testowanie: Karta aplikacji Chrome DevTools, audyt Lighthouse PWA, debugowanie oprogramowania
W następnym artykule z tej serii omówimy Zaawansowane wstrzykiwanie zależności
w Angular, analizując niestandardowe tokeny wtryskowe, dostawców chwiejących się na drzewie,
wielu dostawców, dostawca fabryczny i strategie określania zakresu providedIn.







