서비스 워커 지원을 활성화하면 서비스 워커를 등록하는 것뿐 아니라, 서비스 워커와 상호작용하고 애플리케이션 캐시를 제어할 때 사용할 서비스도 제공됩니다.
SwUpdate 서비스
SwUpdate 서비스를 사용하면 서비스 워커가 애플리케이션에 적용할 수 있는 업데이트를 발견하고 설치할 때 발생하는 이벤트에 접근할 수 있습니다.
SwUpdate 서비스는 다음 세 가지 기능을 지원합니다:
- 서버에서 업데이트된 버전이 감지 되거나, 로컬에 설치되어 사용할 준비가 되었거나, 설치에 실패 했을 때 알림 받기
- 서비스 워커에 서버의 새 업데이트 확인 요청하기
- 서비스 워커에 현재 탭에서 사용할 최신 애플리케이션 버전 활성화 요청하기
버전 업데이트
versionUpdates는 SwUpdate의 Observable 프로퍼티이며, 네 가지 이벤트를 방출합니다:
| 이벤트 타입 | 설명 |
|---|---|
VersionDetectedEvent |
서비스 워커가 서버에서 앱의 새 버전을 감지하고 다운로드를 시작하려고 할 때 발생합니다. |
NoNewVersionDetectedEvent |
서비스 워커가 서버에서 앱 버전을 확인했지만 새 버전을 찾지 못했을 때 발생합니다. |
VersionReadyEvent |
클라이언트가 앱의 새 버전을 활성화할 수 있을 때 발생합니다. 사용자에게 업데이트를 알리거나 페이지 새로고침을 요청할 때 사용할 수 있습니다. |
VersionInstallationFailedEvent |
새 버전 설치에 실패했을 때 발생합니다. 로깅이나 모니터링 용도로 사용할 수 있습니다. |
log-update.service.ts
@Service()
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;
}
});
}
}
업데이트 확인하기
서비스 워커에 서버에 배포된 업데이트가 있는지 확인하도록 요청할 수 있습니다. 서비스 워커는 초기화할 때와 탐색 요청이 발생할 때마다, 즉 사용자가 다른 주소에서 애플리케이션으로 이동할 때 업데이트를 확인합니다. 하지만 사이트가 자주 변경되거나 일정에 따라 업데이트하려면 직접 업데이트를 확인하도록 할 수도 있습니다.
이때는 checkForUpdate() 메서드를 사용하면 됩니다:
check-for-update.service.ts
import {ApplicationRef, inject, Service} from '@angular/core';
import {SwUpdate} from '@angular/service-worker';
import {concat, interval, first} from 'rxjs';
@Service()
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>을 반환합니다.
확인 과정이 실패하면 Promise가 거부됩니다.
애플리케이션 안정화와 서비스 워커 등록
페이지의 초기 렌더링에 나쁜 영향을 주지 않도록, Angular 서비스 워커 서비스는 기본적으로 애플리케이션이 안정화되기를 최대 30초 동안 기다린 후 ServiceWorker 스크립트를 등록합니다.
setInterval()이나 RxJS의 interval() 등으로 업데이트를 계속 폴링하면 애플리케이션이 안정화되지 않습니다. 이 경우 최대 대기 시간인 30초가 지날 때까지 ServiceWorker 스크립트가 브라우저에 등록되지 않습니다.
이 동작은 애플리케이션에서 수행하는 모든 종류의 폴링에 적용됩니다. 자세한 내용은 isStable 문서를 참고하세요.
이 지연을 방지하려면 앞의 예제처럼 애플리케이션이 먼저 안정화되기를 기다린 뒤 업데이트 폴링을 시작하세요. 또는 ServiceWorker에 다른 등록 전략을 정의할 수도 있습니다.
최신 버전으로 업데이트하기
새 버전이 준비되면 페이지를 새로고침해서 기존 탭을 최신 버전으로 업데이트할 수 있습니다. 사용자의 작업을 방해하지 않으려면, 일반적으로 사용자에게 페이지를 새로고침하고 최신 버전으로 업데이트해도 되는지 확인받는 것이 좋습니다:
prompt-update.service.ts
@Service()
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()를 호출하면 페이지를 새로고침하지 않고 탭을 최신 버전으로 업데이트하지만, 이로 인해 애플리케이션이 제대로 동작하지 않을 수 있습니다.
새로고침 없이 업데이트하면 앱 기본 구조와 다른 페이지 리소스 사이에 버전 불일치가 발생할 수 있습니다. 예를 들어 지연 로딩되는 청크의 파일 이름은 버전마다 달라질 수 있습니다.
구체적인 사용 상황에서 안전하다고 확신할 때만 activateUpdate()를 사용하세요.
복구할 수 없는 상태 처리하기
서비스 워커가 클라이언트에 제공하는 애플리케이션 버전이 손상되어, 전체 페이지를 새로고침하지 않으면 복구할 수 없는 상태가 될 수 있습니다.
예를 들어 다음과 같은 상황을 생각해 봅시다:
사용자가 애플리케이션을 처음 열면 서비스 워커가 애플리케이션의 최신 버전을 캐시에 저장합니다. 캐시에 저장된 애플리케이션 리소스가
index.html,main.<main-hash-1>.js,lazy-chunk.<lazy-hash-1>.js라고 가정하겠습니다.사용자가 애플리케이션을 닫고 한동안 열지 않습니다.
시간이 지난 뒤 서버에 애플리케이션의 새 버전이 배포됩니다. 새 버전에는
index.html,main.<main-hash-2>.js,lazy-chunk.<lazy-hash-2>.js파일이 포함되어 있습니다.
중요: 파일 내용이 변경되었으므로 해시도 달라졌습니다. 서버에서는 더 이상 이전 버전을 제공하지 않습니다.
그동안 사용자의 브라우저가 캐시에서
lazy-chunk.<lazy-hash-1>.js를 제거합니다. 브라우저는 디스크 공간을 확보하기 위해 캐시에서 특정 리소스 또는 모든 리소스를 제거할 수 있습니다.사용자가 애플리케이션을 다시 엽니다. 서비스 워커는 이 시점에서 자신이 알고 있는 최신 버전, 즉 이전 버전인
index.html과main.<main-hash-1>.js를 제공합니다.이후 애플리케이션이 지연 로딩 번들인
lazy-chunk.<lazy-hash-1>.js를 요청합니다.서비스 워커는 캐시에서 해당 리소스를 찾지 못합니다. 앞서 브라우저가 제거했기 때문입니다. 서버에서도 가져올 수 없습니다. 서버에는 새 버전의
lazy-chunk.<lazy-hash-2>.js만 있기 때문입니다.
이 상황에서는 서비스 워커가 정상적으로 캐시에 있어야 할 리소스를 제공할 수 없습니다.
해당 애플리케이션 버전이 손상된 상태이며, 페이지를 새로고침하지 않고는 클라이언트 상태를 복구할 방법이 없습니다.
이 경우 서비스 워커는 UnrecoverableStateEvent 이벤트를 보내 클라이언트에 알립니다.
이 알림을 받아 오류를 처리하려면 SwUpdate#unrecoverable을 구독하세요.
handle-unrecoverable-state.service.ts
@Service()
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 서비스 워커 더 알아보기
다음 내용도 함께 살펴보세요: