Angular + PWA: インストール可能なアプリを作成する
Le プログレッシブ ウェブ アプリ (PWA) これらは Web アプリケーション間の架け橋を表します そしてネイティブアプリ。私のようなテクノロジーのおかげで サービスワーカー, il ウェブアプリマニフェスト そして プッシュ通知、PWA が提供する ストアからインストールしたアプリと同等のエクスペリエンスを提供しますが、費用や費用はかかりません。 ネイティブディストリビューションの複雑さ。 Angular は PWA サポートをネイティブに統合します。 Web アプリケーションをインストール可能なアプリに変換するプロセスを作成する シンプルで十分に文書化されています。
シリーズ第 8 回の記事では、 モダンアンギュラー 私たちはあらゆる側面を探ります Angular を使用した PWA の作成: 初期構成から高度な管理まで キャッシュ、オフラインサポートからプッシュ通知、更新メカニズムまで 自動およびカスタムのインストール プロンプト。それぞれをテストおよびデバッグする方法を見ていきます。 Chrome DevTools ツールの機能。
この記事で学べること
- PWA とは何ですか、またネイティブ アプリに比べてどのような利点がありますか
- Angular PWA をセットアップする方法
ng add @angular/pwa - アイコン、色、表示モードに関する Web アプリ マニフェストのカスタマイズ
- Service Worker を構成する
ngsw-config.json - キャッシュ戦略: プリフェッチ、レイジー、ネットワークファースト、キャッシュファースト
- フォールバック ページと IndexedDB によるオフライン サポート
- サービスによるプッシュ通知
SwPush - アップデートを管理する
SwUpdate - カスタムプロンプトをインストールするには
beforeinstallprompt - Chrome DevTools と Lighthouse を使用したテストとデバッグ
最新の Angular シリーズの概要
| # | アイテム | 集中 |
|---|---|---|
| 1 | 角度信号 | きめ細かい応答性 |
| 2 | ゾーンレスの変更検出 | Zone.jsを削除する |
| 3 | 新しいテンプレート @if、@for、@defer | 最新の制御フロー |
| 4 | スタンドアロンコンポーネント | NgModule を使用しないアーキテクチャ |
| 5 | 信号形式 | シグナルを使用したレスポンシブフォーム |
| 6 | SSR と増分水分補給 | サーバーサイドレンダリング |
| 7 | Angular におけるコア Web バイタル | パフォーマンスと指標 |
| 8 | あなたはここにいます - Angular PWA | プログレッシブ Web アプリ |
| 9 | 高度な依存関係の注入 | DIツリーシェイク可能 |
| 10 | Angular 17 から 21 への移行 | 移行ガイド |
1. プログレッシブ Web アプリとは
Una プログレッシブ Web アプリ テクノロジーを使用した Web アプリケーションです 最新のネイティブ アプリと同等のユーザー エクスペリエンスを提供します。用語 「プログレッシブ」とは、ブラウザに関係なく、アプリがすべてのユーザーに対して機能することを意味します をサポートするブラウザでのエクスペリエンスが徐々に改善されています。 高度な機能。
PWA の主な機能
PWA は、次のような一連の機能によって従来の Web アプリケーションと区別されます。 ストア経由で配布されるネイティブ アプリに近づけます。
PWA vs ネイティブ アプリ vs 従来の Web アプリ
| 特性 | ウェブアプリ | PWA | ネイティブアプリ |
|---|---|---|---|
| インストール可能 | No | Si | Si |
| オフラインでも動作します | No | Si | Si |
| プッシュ通知 | No | Si | Si |
| 自動更新 | Si | Si | 店舗経由 |
| ハードウェアアクセス | 限定 | 良い | 完了 |
| 分布 | URL | URL + インストール | アプリストア |
| 開発費 | ベース | ベース | 高い |
| SEO | Si | Si | いいえ (ストアインデックス作成) |
PWA を選択する場合
PWA は、最も多くのユーザーにリーチしたい場合に理想的な選択肢です。 単一のコードベース。電子商取引、ブログ、会社のダッシュボード、ツールに最適です。 生産性と、オフライン アクセスと通知の恩恵を受けるアプリケーション 押します。アプリがハードウェア (高度な Bluetooth、NFC、センサー) へのディープ アクセスを必要とする場合 詳細)、ネイティブ アプリが依然として必要な場合があります。
2. Angular PWA のセットアップ
Angular は、PWA 構成を自動化する公式の回路図を提供します。と 単一のコマンドを実行すると、CLI は必要なすべてのファイル(マニフェスト、 Service Worker とデフォルトのアイコン。
# 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)
app.config.ts での構成
回路図を実行した後、 app.config.ts 更新されています
Service Worker を自動的に登録します。登録は以下でのみ行われます
本番環境とアプリケーションの安定化後。
// 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'
})
]
};
Service Worker と開発環境
Service Worker は開発モードでは無効になっています (isDevMode()) なぜ
積極的なキャッシュはホットリロードとライブリロードを妨げます。テストするには
Service Worker をローカルで使用するには、実稼働ビルドを実行する必要があります。
ng build そして、次のようなHTTPサーバーを使用して静的ファイルを提供します
http-server o npx serve.
3. Web アプリマニフェスト
Il ウェブアプリマニフェスト アプリケーションを説明する JSON ファイルです。 ブラウザとオペレーティング システム。アプリ名、アイコン、 テーマカラーと表示モード。有効なマニフェストがないと、ブラウザは アプリをインストールする機能は提供されていません。
{
"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" }]
}
]
}
マニフェスト表示モード
| モード | 説明 | 使用事例 |
|---|---|---|
standalone |
アプリはアドレス バーのないネイティブ アプリとして表示されます | PWA の最も一般的な選択肢 |
fullscreen |
アプリは画面全体を占め、ブラウザー UI はありません | ゲーム、臨場感あふれるプレゼンテーション |
minimal-ui |
スタンドアロンに似ていますが、最小限のナビゲーション コントロールを備えています | 戻る/進むナビゲーションが必要なアプリ |
browser |
アプリは標準のブラウザタブで開きます | 従来の Web サイト (PWA には推奨されません) |
4. Service Workerの構成
ファイル ngsw-config.json Angular の PWA 構成の中心です。
どのリソースがキャッシュされるか、どのように更新されるか、どのリソースで更新されるかを定義します。
戦略。 Angular はファイルを自動的に生成します ngsw-worker.js 中に
この構成に基づいて実稼働ビルドが行われます。
{
"$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": [
"/**",
"!/__**"
]
}
アセットグループとデータグループの比較
| 財産 | アセットグループ | データグループ |
|---|---|---|
| 範囲 | 静的アプリのリソース (JS、CSS、画像) | APIからの動的データ |
| バージョン管理 | ファイルのハッシュに基づいて | 時間ベース (maxAge) |
| インストールモード | prefetch o lazy |
該当なし |
| 戦略 | 該当なし | freshness o performance |
| アップデート | アプリのバージョンが変わったとき | maxAge とタイムアウトに基づく |
5. キャッシュ戦略
キャッシュ戦略の選択は、パフォーマンスとパフォーマンスのバランスをとるために不可欠です。
データの鮮度。 Angular NGSW は、i の 2 つの主要な戦略をサポートしています。
dataGroups: 鮮度 (ネットワークファースト) e
パフォーマンス (キャッシュファースト)、およびアセットの静的キャッシュ。
キャッシュ戦略の詳細
| 戦略 | 優先度 | 行動 | 使用事例 |
|---|---|---|---|
| プリフェッチ (資産グループ) | キャッシュ | SWのインストール後すぐにすべてのリソースをダウンロードします。 | シェルアプリ、コアJSおよびCSS |
| 怠け者 (資産グループ) | オンデマンド キャッシュ | 要求された場合にのみリソースをキャッシュします | 画像、フォント、二次資産 |
| 鮮度 (データグループ) | ネットワーク | ネットワークを試してください。タイムアウトになった場合はキャッシュを使用する | ユーザープロファイル、通知、リアルタイムデータ |
| パフォーマンス (データグループ) | キャッシュ | キャッシュが利用可能で期限切れでない場合はキャッシュを使用します。それ以外の場合はネットワーク | ブログ記事、製品カタログ、静的データ |
戦略 鮮度 パターンと同等です ネットワークファースト: Service Worker は最初にネットワークを試行し、リクエストがタイムアウト内に失敗した場合は、 指定するには、キャッシュされたバージョンが必要です。戦略 パフォーマンス それはです 代わりに キャッシュファースト: キャッシュされたバージョンがすぐに必要です (有効期限が切れていない場合) バックグラウンドでキャッシュを更新します。
Angular による再検証中の失効化
Angular NGSW はこの戦略をネイティブにサポートしていません 再検証中に失効する、
ただし、戦略を組み合わせることでシミュレーションできます。 パフォーマンス con un
maxAge 簡単な。たとえば、設定 "maxAge": "5m" キャッシュ
データはすぐに提供されますが、5 分後にネットワークに対して新しいリクエストが行われます。
次回のログイン時。より詳細な制御が必要な場合は、その使用を検討してください
直接の キャッシュAPI カスタムサービスワーカーと一緒に。
6. オフラインサポート
PWA の最も重要な機能の 1 つは、PWA がなくても動作する機能です。 インターネット接続。 Angular はリソースのキャッシュを自動的に処理します Service Worker を介して静的ですが、完全なオフライン エクスペリエンスにはこれが必要です 接続ステータス、フォールバック ページ、サーバーの永続性も管理します ローカルデータ。
接続状態の検出
// 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
}
}
IndexedDB によるデータの永続化
構造化データをオフラインで管理するには、 IndexedDB それは最も堅牢なソリューションです。
とは異なり localStorage (同期、制限 5 ~ 10MB)、IndexedDB をサポート
大量のデータ、インデックス付きクエリ、非同期操作。
// 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);
});
}
}
オフラインストレージの制限
- ブラウザシェア: ブラウザは利用可能なストレージを制限しています (通常、ディスク容量の 50 ~ 80%)。アメリカ合衆国
navigator.storage.estimate()利用可能なスペースを確認するには - 立ち退きポリシー: 空き容量が不足している場合、ブラウザによってデータが削除される場合があります。永続ストレージをリクエストするには
navigator.storage.persist() - SSRの安全性: IndexedDB はサーバー側に存在しません。常に使用する
isPlatformBrowserアクセスする前に - 同期の競合: ユーザーがオンラインに戻ると、ローカル データがサーバー データと競合する可能性があります。競合解決戦略を導入する
7. プッシュ通知
プッシュ通知を使用すると、PWA は次のような場合でもユーザーにメッセージを送信できます。
アプリはアクティブではありません。 Angular がサービスを提供 SwPush これは簡素化します
サブスクリプションを管理し、
Webプッシュプロトコル.
プッシュ通知の購読
// 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);
}
}
}
サーバーからの通知の送信
// 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キー
VAPID キー (自発的なアプリケーションサーバーの識別) に使用されます
ブラウザプッシュサービスに対してサーバーを認証します。それらを生成するには、
コマンド npx web-push generate-vapid-keys。公開キーを入力する必要があります
フロントエンドでは、プライベートなものはサーバー上にのみ残ります。決してコミットしないでください
リポジトリ内の秘密キー。
8. アップデート管理
PWA の新しいバージョンをリリースするとき、Service Worker は、
リソースを更新し、ユーザーに通知します。 Angular がサービスを提供
SwUpdate 更新ライフサイクルを適切な方法で管理するため
チェックした。
// 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();
});
}
}
}
PWA アップデートのライフサイクル
| 段階 | SwUpdateイベント | 説明 |
|---|---|---|
| 1. 検出 | VERSION_DETECTED |
SW がサーバー上で新しいバージョンを見つけました |
| 2. ダウンロード | VERSION_INSTALLATION_FAILED |
新しいバージョンのダウンロード中にエラーが発生しました (失敗した場合) |
| 3.準備完了 | VERSION_READY |
新しいバージョンがダウンロードされ、アクティベーションの準備ができました |
| 4. アクティベーション | activateUpdate() |
ユーザーが確認すると、SW は新しいバージョンをアクティブ化します。 |
| 5. リチャージ | location.reload() |
ページがリロードされ、更新されたリソースが表示されます |
回復不能な Service Worker エラー
イベント unrecoverable Service Worker が次のことを行うことができなくなったことを示します。
アプリの現在のバージョンを正しく提供します (たとえば、サーバーが削除しました)
リソースはまだ必要です)。この場合、唯一の解決策は強制的にリロードすることです
ページで完了 document.location.reload()。このイベントを監視する
本番環境で展開の問題を特定します。
9. カスタムインストールプロンプト
PWA が条件を満たすと、ブラウザにインストール バナーが自動的に表示されます。
設置可能性の基準。ただし、ネイティブ バナーはあまり表示されず、制御可能でもありません。
イベントの傍受 beforeinstallprompt という体験を生み出すことができます
専用のボタンとアプリと一貫したデザインを使用してカスタマイズされたインストール。
// 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 のインストール可能性の基準
| 要件 | 説明 |
|---|---|
| HTTPS | アプリは HTTPS (または開発の場合はローカルホスト) 経由で提供される必要があります |
| ウェブアプリマニフェスト | 必ず含める必要があります name, icons (192ピクセル + 512ピクセル)、 start_url, display |
| サービスワーカー | イベントハンドラーを持つアクティブなService Worker fetch |
| まだインストールされていません | アプリがデバイスにすでにインストールされている必要はありません |
| ユーザーインタラクション | ユーザーはドメインを操作している必要があります (クリック、スクロールなど)。 |
10. テストとデバッグ
Service Worker はアクティブのみであるため、PWA をテストするには特定のツールが必要です
実稼働環境では、PWA 機能は開発中に使用できません。
ng serve。 Chrome DevTools と Lighthouse は必要なものをすべて提供します
PWA のあらゆる側面をテストおよびデバッグします。
テスト用のビルドとローカル サービス
# 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 - [アプリケーション] タブ
タブ 応用 Chrome DevTools のメイン パネルです。 PWA のあらゆる側面を検査し、デバッグします。
PWA 用の DevTools パネル
| パネル | 関数 | 確認する |
|---|---|---|
| マニフェスト | 解析されたマニフェストを表示する | 名前、アイコン、表示モード、開始URLが正しい |
| サービスワーカー | 登録SWの状態 | SW がアクティブ、更新が保留中、エラー |
| キャッシュストレージ | キャッシュ内容 | 適切なリソースがキャッシュ内にあり、合計サイズ |
| インデックス付きDB | ローカルデータベース | オフライン データは正常に保持されました |
| ストレージ | ストレージ割り当てと使用量 | 使用済みスペースと利用可能なスペース |
ライトハウス PWA 監査
Lighthouse には、PWA 検証専用のセクションが含まれています。監査は基準をチェックします インストール性、オフラインの最適化、ベスト プラクティス。適切に構成された PWA Lighthouse PWA スコアは次のようになります。 100/100.
ライトハウス PWA チェックリスト
- Service Worker がハンドラーを登録します
fetch - ページはオフラインでもコード 200 で応答します
start_urlマニフェスト内にあり、オフラインでアクセス可能- マニフェストには、
nameoshort_name - マニフェストには少なくとも 192 ピクセルと 512 ピクセルのアイコンが含まれています
- マニフェストは次のように指定します
display(スタンドアロン/フルスクリーン/ミニマル UI) - コンテンツはビューポートに合わせて適切なサイズに調整されます
- メタタグ
theme-colorHTML 内に存在する - ページには、
viewport有効なメタタグ - このサイトはHTTPSを使用しています
- HTTP は HTTPS にリダイレクトされます
Service Worker のデバッグ
// 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"
よくある問題と解決策
- SW が更新されない場合: 開発中に [DevTools] > [Application] > [Service Worker] で [Update on reload] を使用します。
- 永続的な古いキャッシュ: [DevTools] > [Application] > [Storage] > [Clear site data] から完全なストレージをクリアします。
- インストール プロンプトが表示されない場合: [マニフェスト] パネルで、インストール可能性の基準がすべて満たされていることを確認します。
- プッシュ通知が届かない場合: 権限が「付与」されていることを確認します
Notification.permissionサブスクリプションが有効であること - CORS キャッシュ エラー: 外部リソースは、SW によってキャッシュされるように、適切な CORS ヘッダーを使用して提供される必要があります
- SW が「待機中」状態: 新しい SW は、すべてのアプリのタブが閉じられるまで待機します。アメリカ合衆国
skipWaiting()即時アクティベーションを強制するには
概要と次のステップ
プログレッシブ Web アプリは、Web アプリケーションの自然な進化を表します。 数年前まではネイティブ アプリ専用だった機能を提供します。 Angular では、次のサポートが組み込まれているため、PWA の構築がはるかに簡単になります。 Service Worker、インテリジェントなキャッシュ、プッシュ通知、管理された更新。
この記事の重要な概念
- PWA: オフラインサポート、プッシュ通知、自動更新を備えたインストール可能な Web アプリケーション
- 設定:
ng add @angular/pwaマニフェスト、SW 構成、アイコンを生成します - マニフェスト: アプリを説明する JSON ファイル (名前、アイコン、表示モード、開始 URL)
- ngsw-config.json: 資産グループ (静的リソース) とデータグループ (動的 API) の構成
- キャッシング: アセットのプリフェッチ/遅延戦略、API データの鮮度/パフォーマンス
- オフライン: ネットワークステータスの検出、永続化のための IndexedDB、オンラインに戻ったときの同期
- 押す:
SwPushサブスクリプションと通知の受信用、サーバー認証用の VAPID - 更新情報:
SwUpdate新しいリリースを確認、通知、アクティブ化するため - インストールプロンプト:
beforeinstallpromptカスタムセットアップUIの場合 - テスト: Chrome DevTools の [アプリケーション] タブ、Lighthouse PWA 監査、SW デバッグ
シリーズの次の記事では、 高度な依存関係の注入
Angular で、カスタム インジェクション トークン、ツリーシェイク可能なプロバイダーの分析、
マルチプロバイダー、ファクトリープロバイダー、およびスコーピング戦略 providedIn.







