问题
In an Angular (8) app I'd like to add a custom offline page (just a plain simple html-file to begin with).
I have set up my app as a PWA (using @angular/pwa
and configured everything so that it at least works smoothly while being online).
However, I've had a hard time making updates available for PWA users. So, after many hours of try and error I decided to exclude index.html
from the ngsw-config.json
. This has -of course- the effect that index.html
gets loaded every single time (not so bad, 'cause it's so small). If there are any updates index.html
links to different JS-files and these files get loaded immediately. So, as I said before, the PWA works just as I like it to be.
Now I want to display an offline.html
when the user starts the PWA being offline. So I've add offline.html
to ngsw-config.json
and I've created a custom Service Worker including the official ngsw-worker.js
:
importScripts('./ngsw-worker.js');
I'm also using this custom service worker instead of the official one:
ServiceWorkerModule.register('./custom-worker.js', { enabled: true, registrationStrategy: registrationStrategy })
So far, everything still works as expected. Behavior is just like before. Now I wanted to include the offline behavior in my custom worker:
importScripts('./ngsw-worker.js');
self.addEventListener('fetch', function(event) {
return event.respondWith(
caches.match(event.request)
.then(function(response) {
let requestToCache = event.request.clone();
return fetch(requestToCache).then().catch(error => {
// Check if the user is offline first and is trying to navigate to a web page
if (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html')) {
// Return the offline page
return caches.match("offline.html");
}
});
})
);
});
This script comes from: https://stackoverflow.com/a/56738140/4653997
Unfortunately this is the part that doesn't work at all. For now I'm pretty much stuck. I have no idea what do next.
I thought service workers get executed whether index.html
can get loaded or not.
Any help would be appreciated.
回答1:
I've got it working!
In the end it was a relatively simple fetch
event listener I had to add to my custom service worker:
// listen to every fetch event
self.addEventListener('fetch', function (event) {
const request = event.request;
// filter for html document fetches (should only result in one single fetch) --> index.html
if (request.method === "GET" && request.destination === "document") {
// only intercept if there was a problem fetching index.html
event.respondWith(
fetch(request).catch(function (error) {
console.error("[onfetch] Failed. Serving cached offline fallback", error);
// return offline page from cache instead
return caches.match("/assets/offline.html");
}));
}
});
// use all the magic of the Angular Service Worker
importScripts('./ngsw-worker.js');
回答2:
index.html and .js will get cached with service workers. @angular/pwa will do that for you so it will init angular even when offline. You can use that as advantage to check if user is online before it boots up app so you can use APP_INITIALIZER for it.
First register function that is going to be called on app init as following:
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { AppLoadService } from './app-load.service';
export function init_app(appLoadService: AppLoadService) {
return () => appLoadService.initializeApp();
}
@NgModule({
imports: [HttpClientModule],
providers: [
AppLoadService,
{ provide: APP_INITIALIZER, useFactory: init_app, deps: [AppLoadService], multi: true },
]
})
This service is the one that is initially called and will contain method that response to app init. I just added window.location.href as sample here but you can do anything in that if where it checks if browser is online or not.
export class AppLoadModule { }
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class AppLoadService {
constructor(private httpClient: HttpClient) { }
initializeApp(): Promise<any> {
return new Promise((resolve, reject) => {
if (!window.navigator.onLine) {
window.location.href = offlineURL; // this would probably be something like 'yourURL/assets/offline-page.html'
}
resolve();
});
}
}
Note that you'll still need to have offline page in your resources ng-sw.config so you can access it when sw caches it
"resources": {
"files": [
...
"/assets/offline-page.html"
],
来源:https://stackoverflow.com/questions/58163206/angular-pwa-with-custom-offline-page