把 ServiceWorkerModule
导入到你的 AppModule
中不仅会注册 Service Worker,还会提供一些服务,让你能和 Service Worker 通讯,并控制你的应用缓存。
SwUpdate
服务让你能访问一些事件,这些事件会指出 Service Worker 何时发现并安装了可用的更新
SwUpdate
服务支持四个独立的操作:
versionUpdates
是 SwUpdate
的一个 Observable
属性,并且会发出四种事件类型:
事件类型 | 详情 |
---|---|
| 当 Service Worker 在服务器上检测到应用程序的新版本并即将开始下载时发出。 |
| 当 Service Worker 检查了服务器上应用程序的版本并且没有找到新版本时发出。 |
| 当有新版本的应用程序可供客户端激活时发出。它可用于通知用户可用的更新或提示他们刷新页面。 |
| 在新版本安装失败时发出。它可用于日志/监控目的。 |
@Injectable()
export class LogUpdateService {
constructor(updates: SwUpdate) {
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;
}
});
}
}
可以要求 Service Worker 检查是否有任何更新已经发布到了服务器上。Service Worker 会在初始化和每次导航请求(也就是用户导航到应用中的另一个地址)时检查更新。不过,如果你的站点更新非常频繁,或者需要按计划进行更新,你可能会选择手动检查更新。
通过调用 checkForUpdate()
方法来实现:
import { ApplicationRef, Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
import { concat, interval } from 'rxjs';
import { first } from 'rxjs/operators';
@Injectable()
export class CheckForUpdateService {
constructor(appRef: ApplicationRef, updates: SwUpdate) {
// Allow the app to stabilize first, before starting
// polling for updates with `interval()`.
const appIsStable$ = appRef.isStable.pipe(first(isStable => isStable === true));
const everySixHours$ = interval(6 * 60 * 60 * 1000);
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
everySixHoursOnceAppIsStable$.subscribe(() => updates.checkForUpdate());
}
}
该方法返回一个用来表示检查更新已经成功完成的 Promise<boolean>
。这种检查可能会失败,它会导致此 Promise
被拒绝(reject)。
为了避免影响页面的首次渲染,在注册 ServiceWorker 脚本之前,ServiceWorkerModule
默认会在应用程序达到稳定态之前等待最多 30 秒。如果不断轮询更新(比如调用 setInterval()或 RxJS 的 interval())就会阻止应用程序达到稳定态,则直到 30 秒结束之前都不会往浏览器中注册 ServiceWorker 脚本。
注意:
应用中所执行的各种轮询都会阻止它达到稳定态。
可以通过在开始轮询更新之前先等应用达到稳定态来消除这种延迟,如上述例子所示。另外,你还可以为 ServiceWorker 定义不一样的 注册策略。
如果当前标签页需要立即更新到最新的应用版本,可以通过 activateUpdate()
方法来要求立即这么做:
@Injectable()
export class PromptUpdateService {
constructor(updates: SwUpdate) {
updates.available.subscribe(event => {
if (promptUser(event)) {
updates.activateUpdate().then(() => document.location.reload());
}
});
}
}
如果调用 activateUpdate()
而不刷新页面,可能会破坏正在运行的应用中的惰性加载模块,特别是如果惰性加载的模块文件名中使用了哈希时,就会改变每一个版本。所以,建议每当 activateUpdate()
返回的 Promise 被解析时,都刷新一次页面。
在某些情况下,Service Worker 用来为客户端提供服务的应用版本可能处于损坏状态,如果不重新加载整个页面,则无法恢复该状态。
比如,设想以下情形:
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
浏览器可能决定从缓存中清退特定(或所有)资源,以便回收磁盘空间。 用户再次打开本应用。此时,Service Worker 将提供它所知的最新版本,当然,实际上对我们是旧版本(index.html
和 main.<main-hash-1>.js
)。 在稍后的某个时刻,该应用程序请求惰性捆绑包 lazy-chunk.<lazy-hash-1>.js
。 Service Worker 无法在缓存中找到该资产(请记住浏览器已经将其清退了)。它也无法从服务器上获取它(因为服务器现在只有较新版本的 lazy-chunk.<lazy-hash-2>.js
)。 在上述情况下,Service Worker 将无法提供通常会被缓存的资产。该特定的应用程序版本已损坏,并且无法在不重新加载页面的情况下修复客户端的状态。在这种情况下,Service Worker 会通过发送 UnrecoverableStateEvent
事件来通知客户端。可以订阅 SwUpdate#unrecoverable
以得到通知并处理这些错误。
@Injectable()
export class HandleUnrecoverableStateService {
constructor(updates: SwUpdate) {
updates.unrecoverable.subscribe(event => {
notifyUser(
'An error occurred that we cannot recover from:\n' +
event.reason +
'\n\nPlease reload the page.'
);
});
}
}