Внести вклад
Расширенная экосистема
Service Workers и PWA

Взаимодействие с Service Worker

Включение поддержки Service Worker не просто регистрирует его; это также предоставляет сервисы, которые вы можете использовать для взаимодействия с Service Worker и управления кэшированием вашего приложения.

Сервис SwUpdate

Сервис SwUpdate предоставляет доступ к событиям, сообщающим о том, что Service Worker обнаружил и устанавливает доступное обновление для вашего приложения.

Сервис SwUpdate поддерживает три отдельные операции:

  • Получение уведомлений, когда обновленная версия обнаружена на сервере, установлена и готова к использованию локально или когда установка не удалась.
  • Запрос к Service Worker на проверку наличия новых обновлений на сервере.
  • Запрос к Service Worker на активацию последней версии приложения для текущей вкладки.

Обновления версий

versionUpdates — это свойство Observable сервиса SwUpdate, которое генерирует события пяти типов:

Типы событий Подробности
VersionDetectedEvent Генерируется, когда Service Worker обнаружил новую версию приложения на сервере и собирается начать её загрузку.
NoNewVersionDetectedEvent Генерируется, когда Service Worker проверил версию приложения на сервере и не нашел новой версии.
VersionReadyEvent Генерируется, когда новая версия приложения доступна для активации клиентами. Может использоваться для уведомления пользователя о доступном обновлении или предложения обновить страницу.
VersionInstallationFailedEvent Генерируется, когда установка новой версии завершилась неудачей. Может использоваться для целей логирования/мониторинга.
VersionFailedEvent Генерируется, когда версия сталкивается с критическим сбоем (например, ошибки хеша), который затрагивает всех клиентов, использующих эту версию. Предоставляет детали ошибки для отладки и прозрачности.

log-update.service.ts

@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;
      }
    });
  }
}

Проверка обновлений

Можно попросить Service Worker проверить, были ли развернуты какие-либо обновления на сервере. Service Worker проверяет наличие обновлений во время инициализации и при каждом навигационном запросе — то есть, когда пользователь переходит с другого адреса на ваше приложение. Однако вы можете выбрать проверку обновлений вручную, если у вас часто меняющийся сайт или вы хотите, чтобы обновления происходили по расписанию.

Сделайте это с помощью метода 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);
      }
    });
  }
}

Этот метод возвращает Promise<boolean>, который указывает, доступно ли обновление для активации. Проверка может завершиться неудачей, что приведет к отклонению (rejection) Promise.

Стабилизация и регистрация Service Worker

Чтобы избежать негативного влияния на начальный рендеринг страницы, по умолчанию сервис Angular Service Worker ожидает до 30 секунд стабилизации приложения перед регистрацией скрипта ServiceWorker.

Постоянный опрос обновлений, например, с помощью setInterval() или interval() из RxJS, предотвращает стабилизацию приложения, и скрипт ServiceWorker не регистрируется в браузере до тех пор, пока не будет достигнут верхний предел в 30 секунд.

Это верно для любого вида опроса, выполняемого вашим приложением. Ознакомьтесь с документацией isStable для получения дополнительной информации.

Избегайте этой задержки, дожидаясь сначала стабилизации приложения перед началом опроса обновлений, как показано в предыдущем примере. В качестве альтернативы вы можете определить другую стратегию регистрации для ServiceWorker.

Обновление до последней версии

Вы можете обновить существующую вкладку до последней версии, перезагрузив страницу, как только новая версия будет готова. Чтобы не прерывать работу пользователя, обычно хорошей идеей является спросить пользователя и позволить ему подтвердить, что можно перезагрузить страницу и обновиться до последней версии:

prompt-update.service.ts

@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();
        }
      });
  }
}

Безопасность обновления без перезагрузки

Вызов activateUpdate() обновляет вкладку до последней версии без перезагрузки страницы, но это может нарушить работу приложения.

Обновление без перезагрузки может создать несоответствие версий между оболочкой приложения (application shell) и другими ресурсами страницы, такими как лениво загружаемые чанки (lazy-loaded chunks), имена файлов которых могут меняться между версиями.

Вам следует использовать activateUpdate() только в том случае, если вы уверены, что это безопасно для вашего конкретного случая использования.

Обработка невосстановимого состояния

В некоторых случаях версия приложения, используемая Service Worker для обслуживания клиента, может находиться в сломанном состоянии, которое невозможно восстановить без полной перезагрузки страницы.

Например, представьте следующий сценарий:

  1. Пользователь открывает приложение впервые, и Service Worker кэширует последнюю версию приложения. Предположим, кэшированные ресурсы приложения включают index.html, main.<main-hash-1>.js и lazy-chunk.<lazy-hash-1>.js.

  2. Пользователь закрывает приложение и не открывает его некоторое время.

  3. Спустя некоторое время на сервер развертывается новая версия приложения. Эта более новая версия включает файлы index.html, main.<main-hash-2>.js и lazy-chunk.<lazy-hash-2>.js.

ВАЖНО: Хеши теперь другие, так как содержимое файлов изменилось. Старая версия больше недоступна на сервере.

  1. Тем временем браузер пользователя решает удалить lazy-chunk.<lazy-hash-1>.js из своего кэша. Браузеры могут решить удалить конкретные (или все) ресурсы из кэша для освобождения места на диске.

  2. Пользователь снова открывает приложение. Service Worker отдает последнюю известную ему на данный момент версию, а именно старую версию (index.html и main.<main-hash-1>.js).

  3. В какой-то момент позже приложение запрашивает ленивый бандл lazy-chunk.<lazy-hash-1>.js.

  4. Service Worker не может найти ресурс в кэше (помните, что браузер удалил его). Также он не может получить его с сервера (потому что на сервере теперь есть только lazy-chunk.<lazy-hash-2>.js из более новой версии).

В описанном выше сценарии Service Worker не может предоставить ресурс, который обычно был бы закэширован. Эта конкретная версия приложения сломана, и нет способа исправить состояние клиента без перезагрузки страницы. В таких случаях Service Worker уведомляет клиента, отправляя событие UnrecoverableStateEvent. Подпишитесь на SwUpdate#unrecoverable, чтобы получать уведомления и обрабатывать эти ошибки.

handle-unrecoverable-state.service.ts

@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.',
      );
    });
  }
}

Подробнее об Angular Service Worker

Вас также может заинтересовать следующее: