Ativar o suporte a service worker faz mais do que apenas registrar o service worker; ele também fornece services que você pode usar para interagir com o service worker e controlar o caching da sua aplicação.
Service SwUpdate
O service SwUpdate dá acesso a eventos que indicam quando o service worker descobre e instala uma atualização disponível para sua aplicação.
O service SwUpdate suporta três operações separadas:
- Receber notificações quando uma versão atualizada é detectada no servidor, instalada e pronta para ser usada localmente ou quando uma instalação falha.
- Solicitar ao service worker que verifique o servidor por novas atualizações.
- Solicitar ao service worker que ative a última versão da aplicação para a aba atual.
Atualizações de versão
O versionUpdates é uma property Observable de SwUpdate e emite cinco tipos de evento:
| Tipos de evento | Detalhes |
|---|---|
VersionDetectedEvent |
Emitido quando o service worker detectou uma nova versão da aplicação no servidor e está prestes a começar a baixá-la. |
NoNewVersionDetectedEvent |
Emitido quando o service worker verificou a versão da aplicação no servidor e não encontrou uma nova versão. |
VersionReadyEvent |
Emitido quando uma nova versão da aplicação está disponível para ser ativada pelos clients. Pode ser usado para notificar o usuário de uma atualização disponível ou solicitar que atualize a página. |
VersionInstallationFailedEvent |
Emitido quando a instalação de uma nova versão falhou. Pode ser usado para fins de registro/monitoramento. |
VersionFailedEvent |
Emitido quando uma versão encontra uma falha crítica (como erros de hash corrompido) que afeta todos os clients usando essa versão. Fornece detalhes do erro para depuração e transparência. |
log-update.service.ts
import {inject, Injectable} from '@angular/core';import {SwUpdate, VersionReadyEvent} from '@angular/service-worker';@Injectable({providedIn: 'root'})export class LogUpdateService { private updates = inject(SwUpdate); constructor() { this.updates.versionUpdates.subscribe((evt) => { switch (evt.type) { case 'VERSION_DETECTED': console.log(`Downloading new app version: ${evt.version.hash}`); break; case 'VERSION_READY': console.log(`Current app version: ${evt.currentVersion.hash}`); console.log(`New app version ready for use: ${evt.latestVersion.hash}`); break; case 'VERSION_INSTALLATION_FAILED': console.log(`Failed to install app version '${evt.version.hash}': ${evt.error}`); break; case 'VERSION_FAILED': console.log(`Version '${evt.version.hash}' failed with error: ${evt.error}`); break; } }); }}
Verificar atualizações
É possível solicitar ao service worker que verifique se alguma atualização foi implantada no servidor. O service worker verifica atualizações durante a inicialização e em cada requisição de navegação — ou seja, quando o usuário navega de um endereço diferente para sua aplicação. No entanto, você pode optar por verificar manualmente por atualizações se você tem um site que muda com frequência ou quer que atualizações aconteçam em um cronograma.
Faça isso com o method checkForUpdate():
check-for-update.service.ts
import {ApplicationRef, inject, Injectable} from '@angular/core';import {SwUpdate} from '@angular/service-worker';import {concat, interval} from 'rxjs';import {first} from 'rxjs/operators';@Injectable({providedIn: 'root'})export class CheckForUpdateService { private appRef = inject(ApplicationRef); private updates = inject(SwUpdate); constructor() { // Allow the app to stabilize first, before starting // polling for updates with `interval()`. const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable === true)); const everySixHours$ = interval(6 * 60 * 60 * 1000); const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$); everySixHoursOnceAppIsStable$.subscribe(async () => { try { const updateFound = await this.updates.checkForUpdate(); console.log(updateFound ? 'A new version is available.' : 'Already on the latest version.'); } catch (err) { console.error('Failed to check for updates:', err); } }); }}
Este method retorna uma Promise<boolean> que indica se uma atualização está disponível para ativação.
A verificação pode falhar, o que causará uma rejeição da Promise.
Estabilização e registro do service worker
Para evitar afetar negativamente a renderização inicial da página, por padrão o service do service worker do Angular aguarda até 30 segundos para a aplicação estabilizar antes de registrar o ServiceWorker script.
Verificar constantemente por atualizações, por exemplo, com setInterval() ou interval() do RxJS, impede a aplicação de estabilizar e o ServiceWorker script não é registrado no browser até que o limite superior de 30 segundos seja atingido.
Isso é verdade para qualquer tipo de verificação feita pela sua aplicação. Verifique a documentação isStable para mais informações.
Evite esse atraso aguardando que a aplicação estabilize primeiro, antes de começar a verificar por atualizações, como mostrado no exemplo anterior. Alternativamente, você pode querer definir uma estratégia de registro diferente para o ServiceWorker.
Atualizando para a última versão
Você pode atualizar uma aba existente para a última versão recarregando a página assim que uma nova versão estiver pronta. Para evitar interromper o progresso do usuário, é geralmente uma boa ideia solicitar ao usuário e deixá-lo confirmar que está tudo bem recarregar a página e atualizar para a última versão:
prompt-update.service.ts
import {inject, Injectable} from '@angular/core';import {filter, map} from 'rxjs/operators';import {SwUpdate, VersionReadyEvent} from '@angular/service-worker';function promptUser(event: VersionReadyEvent): boolean { return true;}@Injectable({providedIn: 'root'})export class PromptUpdateService { constructor() { const swUpdate = inject(SwUpdate); swUpdate.versionUpdates .pipe(filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY')) .subscribe((evt) => { if (promptUser(evt)) { // Reload the page to update to the latest version. document.location.reload(); } }); // ... const updatesAvailable = swUpdate.versionUpdates.pipe( filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'), map((evt) => ({ type: 'UPDATE_AVAILABLE', current: evt.currentVersion, available: evt.latestVersion, })), ); }}
Segurança de atualizar sem recarregar
Chamar activateUpdate() atualiza uma aba para a última versão sem recarregar a página, mas isso pode quebrar a aplicação.
Atualizar sem recarregar pode criar uma incompatibilidade de versão entre o shell da aplicação e outros recursos da página, como chunks com lazy loading, cujos nomes de arquivo podem mudar entre versões.
Você deve usar activateUpdate() apenas se tiver certeza de que é seguro para seu caso de uso específico.
Lidando com um estado irrecuperável
Em alguns casos, a versão da aplicação usada pelo service worker para servir um client pode estar em um estado quebrado que não pode ser recuperado sem um recarregamento completo da página.
Por exemplo, imagine o seguinte cenário:
Um usuário abre a aplicação pela primeira vez e o service worker armazena em cache a última versão da aplicação. Suponha que os assets em cache da aplicação incluem
index.html,main.<main-hash-1>.jselazy-chunk.<lazy-hash-1>.js.O usuário fecha a aplicação e não a abre por um tempo.
Depois de algum tempo, uma nova versão da aplicação é implantada no servidor. Esta versão mais recente inclui os arquivos
index.html,main.<main-hash-2>.jselazy-chunk.<lazy-hash-2>.js.
IMPORTANTE: Os hashes são diferentes agora, porque o conteúdo dos arquivos mudou. A versão antiga não está mais disponível no servidor.
Enquanto isso, o browser do usuário decide remover
lazy-chunk.<lazy-hash-1>.jsde seu cache. Browsers podem decidir remover recursos específicos (ou todos) de um cache para recuperar espaço em disco.O usuário abre a aplicação novamente. O service worker serve a última versão conhecida por ele neste ponto, ou seja, a versão antiga (
index.htmlemain.<main-hash-1>.js).Em algum momento posterior, a aplicação solicita o bundle lazy,
lazy-chunk.<lazy-hash-1>.js.O service worker não consegue encontrar o asset no cache (lembre-se de que o browser o removeu). Nem consegue recuperá-lo do servidor (porque o servidor agora só tem
lazy-chunk.<lazy-hash-2>.jsda versão mais recente).
No cenário anterior, o service worker não consegue servir um asset que normalmente seria armazenado em cache.
Essa versão específica da aplicação está quebrada e não há como corrigir o estado do client sem recarregar a página.
Em tais casos, o service worker notifica o client enviando um evento UnrecoverableStateEvent.
Inscreva-se em SwUpdate#unrecoverable para ser notificado e lidar com esses erros.
handle-unrecoverable-state.service.ts
import {inject, Injectable} from '@angular/core';import {SwUpdate} from '@angular/service-worker';function notifyUser(message: string): void {}@Injectable({providedIn: 'root'})export class HandleUnrecoverableStateService { private updates = inject(SwUpdate); constructor() { this.updates.unrecoverable.subscribe((event) => { notifyUser( 'An error occurred that we cannot recover from:\n' + event.reason + '\n\nPlease reload the page.', ); }); }}
Mais sobre Angular service workers
Você também pode se interessar pelo seguinte: