昨天,我們看到了觸發 MCP 伺服器工具並生成檔案的不同提示詞。
今天我們將展示具體的實作方式。理解這些程式碼非常重要,這樣我們才能要求 AI 進一步微調架構,甚至在需要時親自修復錯誤。
PwaUpdateService 實作了 Service Worker 事件以進行檢查更新、錯誤復原以及背景輪詢。
@Service()
export class PwaUpdateService {
readonly #swUpdate = inject(SwUpdate);
readonly #window = inject(WINDOW);
readonly updateAvailable = toSignal(
this.#window && this.#swUpdate.isEnabled
? this.#swUpdate.versionUpdates.pipe(
filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
map(() => true),
)
: EMPTY,
{ initialValue: false },
);
async reloadPage(): Promise<void> {
if (this.#window) {
if (this.#swUpdate.isEnabled) {
await this.#swUpdate.activateUpdate();
}
this.#window.location.reload();
}
}
}
updateAvailable 將 Observable 轉換為 Signal,這樣元件就不需要匯入 AsyncPipe 來解析 Observable 以取得數值。
updateAvailable 的初始值為 false,表示目前沒有更新。當 this.#swUpdate.versionUpdates 發出 VersionReadyEvent 時,該 Signal 會變為 true 並顯示 PWA 橫幅(banner)。
reload 方法會重新載入視窗。這是一項安全的操作,因為 #window 參考了全域的 window 物件。
readonly #destroyRef$ = inject(DestroyRef);
constructor() {
if (this.#window && this.#swUpdate.isEnabled) {
this.#swUpdate.unrecoverable
.pipe(takeUntilDestroyed(this.#destroyRef$))
.subscribe(() => this.#window?.location.reload());
}
}
當 Service Worker 發出不可復原(unrecoverable)的事件時,視窗會自動重新載入以獲取新資產並嘗試復原。我們注入了 DestroyRef 並使用 takeUntilDestroyed 操作符來自動取消訂閱。
正式環境的應用程式每隔 1 小時會進行輪詢以檢查更新。如果有更新,系統會顯示 PWA 橫幅元件以提示下載最新版本。
import { InjectionToken } from '@angular/core';
export const PWA_CHECK_INTERVAL = new InjectionToken<number>('PWA_CHECK_INTERVAL', {
providedIn: 'root',
factory: () => {
const milliseconds = 1000;
const seconds = 60;
return 1 * seconds * seconds * milliseconds;
},
});
我們定義了一個注入權杖 PWA_CHECK_INTERVAL,並在工廠函式中回傳該數值。在開發測試與模擬資料時,將此間隔時間覆寫為較短的數值可大幅簡化流程。
readonly #destroyRef$ = inject(DestroyRef);
readonly #pwaCheckInterval = inject(PWA_CHECK_INTERVAL);
readonly #appRef = inject(ApplicationRef);
constructor() {
const isAppStable$ = this.#appRef.isStable.pipe(
filter((isStable) => isStable),
take(1),
);
const polling$ = interval(this.#pwaCheckInterval);
concat(isAppStable$, polling$)
.pipe(
exhaustMap(() =>
from(this.#swUpdate.checkForUpdate()).pipe(
catchError((e) => {
console.error(e);
return EMPTY;
}),
),
),
takeUntilDestroyed(this.#destroyRef$),
)
.subscribe();
}
isAppStable$ Observable 會等待應用程式完全載入完成後透過 take(1) 自動取消訂閱。接著,polling$ 每 1 小時檢查一次更新。當找到新更新時,exhaustMap 會發出 true,否則發出 false。
updateAvailable 會訂閱 this.#swUpdate.versionUpdates,並更新為 true。同樣地,PWA 橫幅元件將會出現,我們可以點擊按鈕來重新載入視窗。
這是不是很棒呢?
安裝 PWA 依賴套件(因為我想節省一些 token):
@Component({
selector: 'app-pwa-update-banner',
template: `
@if (pwaUpdateService.updateAvailable()) {
<div class="pwa-banner">
<span class="pwa-text">A new version is available!</span>
<button (click)="pwaUpdateService.reloadPage()" class="pwa-button">Reload</button>
</div>
}
`,
styleUrl: './pwa-update-banner.css',
})
export class PwaUpdateBanner {
readonly pwaUpdateService = inject(PwaUpdateService);
}
當新部署完成後,只要 pwaUpdateService.updateAvailable() 為 true,PWA 橫幅元件就會出現。
當使用者點擊 Reload 按鈕時,視窗會重新載入以獲取新的資產與正式版打包檔案。
當服務新增、修改或刪除程式碼時,我們需要確保各項測試案例都不會失敗,且具備良好的程式碼涵蓋率。
該元件經過了全面的測試,因為程式碼測試涵蓋率達到了 100%。
我們已成功展示了 PWA 的實作方式,它大幅提升了應用程式的使用者體驗。
明天,我們將展示如何在應用程式中實作 Angular Aria。Angular Aria 在 Angular v22 中已經趨於穩定,我想藉由學習它來建立肌肉記憶。這一次,我會使用 tdd 建立測試案例的架構,並親自編寫增強功能的程式碼。最後,套用 code-review 針對 PRD 和 ADR 比對 Git commit SHA,以確保沒有範圍蔓延與遺漏的需求。
相關資源:
Angular Service Worker 與 PWA
Angular Signals 與 RxJS 互通性 (Interop)