Issue
I have an Angular 8 PWA app in production. I make a lot of updates regularly, so I need to find a way for users to get those updates when they open the app.
Without special action, the app won't update. If you open the app on your browser, it will show the version from when you last opened the app, even if I have pushed a new bundle to production. It seems this is an unfortunate result of PWA-functionality.
I am hosting with AWS Amplify.
To solve this (I had asked a question about it here), I have tried using swUpdate.
The problem is it works way too slow. It will reload the browser if there is an update--but it takes a while to do that. It regularly takes several seconds after a user opens the app for the reload to happen. So you open the app, and 4 to 6 seconds later the app reloads with the new version.
Is there a way to make swUpdate go faster? Or an alternative way to load the new app version?
Here's my code, which I think is a straightforward implementation:
app.component.ts:
import { Component } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(private swUpdate: SwUpdate) {
title = 'Great App'
swUpdate.available.subscribe(event => {
swUpdate.activateUpdate().then(() => {
window.location.reload();
console.log('there is an Update! Reloading now.')
});
})
if (!this.swUpdate.isEnabled) {
console.log('Not going to update');
}
}
But this is not working well, because the reload often happens several seconds after the user goes to the app (even on good internet connection).
I know that I could also show a message to people saying "Want to refresh with a new version?" But this would not address the underlying problem of how slow swUpdate currently works.
Solution
Problem
There are 4 different registration strategies for a Service Worker in Angular, which determines when it will be registered with the browser.
registerWhenStable:<timeout>
: Register as soon as the application stabilizes (no pending micro-/macro-tasks) but no later than milliseconds. If the app hasn't stabilized after milliseconds (for example, due to a recurrent asynchronous task), the ServiceWorker will be registered anyway. If is omitted, the ServiceWorker will only be registered once the app stabilizes.registerImmediately
: Register immediately.registerWithDelay:<timeout>
: Register with a delay of milliseconds. For example, use registerWithDelay:5000 to register the ServiceWorker after 5 seconds. If is omitted, is defaults to 0, which will register the ServiceWorker as soon as possible but still asynchronously, once all pending micro-tasks are completed.An Observable factory function
: A function that returns an Observable. The function will be used at runtime to obtain and subscribe to the Observable and the ServiceWorker will be registered as soon as the first value is emitted.
NOTE: Angular is using registerWhenStable:30000
as default. That means that it will first wait for application to stabilizes and then it will register a Service Worker (or it will register the Service Worker after 30sec if the application does not stabilize until then).
Solution
Instead of default registerWhenStable:30000
, you can set registerImmediately
strategy. Then you can for example add APP_INITIALIZER and inside it check if there is a new version and load it. In that case, user will not see the old version if a new version is available.
app.module.ts
import { APP_INITIALIZER } from '@angular/core';
import { ServiceWorkerModule, SwUpdate } from '@angular/service-worker';
...
function initializeApp(): Promise<any> {
return new Promise(async (resolve, reject) => {
try {
// Check if Service Worker is supported by the Browser
if (this.swUpdate.isEnabled) {
const isNewVersion = await this.swUpdate.checkForUpdate();
// Check if the new version is available
if (isNewVersion) {
const isNewVersionActivated = await this.swUpdate.activateUpdate();
// Check if the new version is activated and reload the app if it is
if (isNewVersionActivated) window.location.reload();
resolve(true);
}
resolve(true);
}
resolve(true);
} catch (error) {
window.location.reload();
}
});
}
...
@NgModule({
...
imports: [
...,
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
registrationStrategy: 'registerImmediately',
}),
],
providers: [
...,
{ provide: APP_INITIALIZER, useFactory: initializeApp, deps: [SwUpdate], multi: true },
],
})
export class AppModule {}
Answered By - NeNaD
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.