Edit to an Edit:
See the answer below. Angular SW was actually functioning/hashing, etc just fine. The problem was the server was re-hashing certain bundles, causing a mismatch, and therefore the SW would constantly show a mismatch between the client and the server.
Edit:
Maybe I need to be clearer. I referenced a question and linked to it, and seem to be getting downvoted because of it, yet with no comments given. The accepted answer to that question did not solve my issue, and was a pure javascript based approach. I need help coming up with an ANGULAR way to solve the following problem:
After a page refresh, the service worker shows a new build. After Chrome is closed and the app is reopened, the old build shows instead of the new build.
As noted below, I implemented the accepted answer to the referenced question and it did not solve the issue.
Original Post:
Look, I have problem that others have come across before with version control via a Service Worker. As best noted in the following question:
Angular Service Worker intermittently serving old version of application
I have the exact same problem - initial client refresh serves the new build, leaving the window and coming back to it shows the old build. Another refresh and another leave and come back seems to then make the new build persist. This is immensely confusing.
The provided solution to above answer, however, seems to indicate that we should make a separate .js file to control the update/removal/registration behavior of our service worker. I have a problem with that approach seeing as the whole point of Angular and our ngsw-config file is to control the service worker from that file alone.
My question is simple: Is there an Angular approach to unregister all service workers, then reload the URL once we're done?
Based on this article: https://love2dev.com/blog/how-to-uninstall-a-service-worker/
I created and tried to use the following code:
window.navigator.serviceWorker.getRegistrations().then( (registrations) => {
for (let registration of registrations) {
registration.unregister().then(() => {
//The problem line
return window.navigator.serviceWorker.clients.matchAll();
}).then((clients) => {
clients.forEach(client => {
if (client.url && "navigate" in client) {
client.navigate(client.url);
}
});
});
}
});
Where my window service is the following:
import { isPlatformBrowser } from '@angular/common';
import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core';
/* Create a new injection token for injecting the window into a component. */
export const WINDOW = new InjectionToken('WindowToken');
/* Define abstract class for obtaining reference to the global window object. */
export abstract class WindowRef {
get nativeWindow(): Window | Object {
throw new Error('Not implemented.');
}
}
/* Define class that implements the abstract class and returns the native window object. */
export class BrowserWindowRef extends WindowRef {
constructor() {
super();
}
get nativeWindow(): Window | Object {
return window;
}
}
/* Create an factory function that returns the native window object. */
export function windowFactory(browserWindowRef: BrowserWindowRef, platformId: Object): Window | Object {
if (isPlatformBrowser(platformId)) {
return browserWindowRef.nativeWindow;
}
return new Object();
}
/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */
export const browserWindowProvider: ClassProvider = {
provide: WindowRef,
useClass: BrowserWindowRef
};
/* Create an injectable provider that uses the windowFactory function for returning the native window object. */
export const windowProvider: FactoryProvider = {
provide: WINDOW,
useFactory: windowFactory,
deps: [ WindowRef, PLATFORM_ID ]
};
/* Create an array of providers. */
export const WINDOW_PROVIDERS = [
browserWindowProvider,
windowProvider
];
So I can access the window as an injectable.
The problem is that I cannot access the clients property of the service worker - it seems that it is only accessible in a service worker file itself.
So now my approach is the following (which uses sort of what Angular recommends on their docs, part of the above approach, and my own code). Please ignore the debuggers.
const appIsStable$ = appRef.isStable.pipe(takeWhile(isStable => isStable === true));
const everyMinute$ = interval(60*1000);
const swUpdateCheck$ = concat(appIsStable$, everyMinute$);
swUpdateCheck$.subscribe(() => {
debugger;
swUpdate.checkForUpdate().then(() => {
debugger;
console.log('new version available');
if(!this.dialog.getDialogById('new-version')) {
this._dialog.openDialog('new-version', 'new-version');
this._dual.nVDialogData$.subscribe((res) => {
if(res){
//this.window.navigator.serviceWorker.getRegistrations().then( (registrations) => {
debugger;
// for (let registration of registrations) {
// debugger;
// registration.unregister();
// // unregister() every old registration here...
// // force page reload here necessary?...
// }
this.swUpdate.activateUpdate().then(() => {
this.window.location.reload();
//Is this what will trigger a new controller?
});
//});
}
});
}
}, (rej) => {
debugger;
});
});
And this works... I actually get my alert dialog like so:
The problem is, that if I hit 'Get new build' - which just reloads my location - One minute later, I get the same window showing up...
In the developer console, I can see that I'm still running the same service worker. So refreshing the app didn't actually get me a new worker.
If I uncomment out some code like so (registration loop taken from accepted answer to referenced question above), I successfully unregister the old worker and register a new worker, but still 1 minute later, I keep getting this pop up about a new build, even though I have a brand new worker and my app shows that I am on the new build.
swUpdateCheck$.subscribe(() => {
debugger;
swUpdate.checkForUpdate().then(() => {
debugger;
console.log('new version available');
if(!this.dialog.getDialogById('new-version')) {
this._dialog.openDialog('new-version', 'new-version');
this._dual.nVDialogData$.subscribe((res) => {
if(res){
this.window.navigator.serviceWorker.getRegistrations().then( (registrations) => {
debugger;
for (let registration of registrations) {
debugger;
registration.unregister();
// unregister() every old registration here...
// force page reload here necessary?...
}
this.swUpdate.activateUpdate().then(() => {
this.window.location.reload();
//Is this what will trigger a new controller?
});
});
}
});
}
}, (rej) => {
debugger;
});
});
What is going on here? Why isn't the update actually applying in either scenario?
The only thing that makes an update apply correctly is clearing application data then empty cache and hard reload.
What am I doing wrong? To my knowledge the new build should persist correctly after it is shown post refresh. Am I wrong about assuming that?
Any help would be appreciated. Thank you.