Angular Service Worker SwUpdate.available not triggered
Asked Answered
V

8

53

I'm having a hard time integrating angulars service worker into my application. I followed the guide and it works so far. I can create a shortcut on my homescreen and launch into my app. The problem is that my app somehow doesn't update. If I change the name of a button, build the app and put it onto my server the app still shows the old version until I hit F5 (restarting the app doesn't help either).

I tried to put the following code into my ngOnInot of my app but it didn't help

ngOnInit() {
if (this._SwUpdate.isEnabled) {

  setInterval( () => {
    this._SwUpdate.checkForUpdate().then(() => console.log('checking for updates'));
  }, this.updateInterval);

  this._SwUpdate.available.subscribe(() => {

    console.log('update found');

    this._SwUpdate.activateUpdate().then(() => {
      console.log('updated');
      window.location.reload();
    });

  });

}

}

The app is running on my apache2 linux machine. Is my apache caching something or why doesn't my app realize that there is a new version?

Thanks in advance for your help :)

Edit:

My ngsw-config.json

{
  "index": "/index.html",
  "assetGroups": [{
    "name": "roomPlan",
    "installMode": "prefetch",
    "resources": {
      "files": [
        "/index.html",
        "/*.css",
        "/*.js"
      ]
    }
  }, {
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {
      "files": [
        "/assets/**"
      ]
    }
  }]
}

Edit 2:

It works if I run the app local using "http-server" but when I copy the files over to my apache it doesn't detect the update. In the networking tab I can see that the interval works, the app gets a new "ngsw.json" from the server every 3 seconds. If I update my app I can see that there are new hash values inside of the response for "ngsw.json". After that the browser loads the new "index.html" and "main.***.js" from my server but the app doesn't apply the new version. According to my code it should say "update found" but nothing happens.

Vail answered 21/6, 2018 at 12:44 Comment(1)
Angular docs are the best resource for this, pleaser refer this pageHirohito
A
61

You will probably need to tell the service worker to check the server for updates, I usually use a service for this:

export class UpdateService {

  constructor(public updates: SwUpdate) {
    if (updates.isEnabled) {
      interval(6 * 60 * 60).subscribe(() => updates.checkForUpdate()
        .then(() => console.log('checking for updates')));
    }
  }

  public checkForUpdates(): void {
    this.updates.available.subscribe(event => this.promptUser());
  }

  private promptUser(): void {
    console.log('updating to new version');
    this.updates.activateUpdate().then(() => document.location.reload()); 
  }

In your app-component.ts:

  constructor(private sw: UpdateService) {
    // check the service worker for updates
    this.sw.checkForUpdates();
  }

For whatever reason, Angular sometimes does not register the service worker properly. So you can modify `main.ts` :

Replace:

platformBrowserDynamic().bootstrapModule(AppModule);

With:

platformBrowserDynamic().bootstrapModule(AppModule).then(() => {
  if ('serviceWorker' in navigator && environment.production) {
    navigator.serviceWorker.register('ngsw-worker.js');
  }
}).catch(err => console.log(err));
Astaire answered 21/6, 2018 at 12:54 Comment(31)
It seems like the promise returned from checkForUpdate() is never fullfilled, the interval works but the 'checking for updates' never appears in my console.Vail
Are you 100% sure the service worker has been properly registered?Astaire
@MadMurl0c check the updated answer, I modified the promptUser method and added something to change in main.ts and here is an example in a demo projectAstaire
The service worker seems to work according to dev tools and after resetting my chrome data I get the "checking for updates" in my console, the app doesn't update though, it seems like he's checking for updates but never realizing that there is an updated version.. Could my apache somehow cache the version number so that my service worker doesn't know that there is a new one?Vail
Thanks, now the service worker gets started correctly and not only on every ~3rd try, but it still doesn't update although it atleast checks for updates nowVail
did you see the update to promptUser which now activates the update using this.updates.activateUpdate().then(() => document.location.reload())); Astaire
Yes I saw that and I integrated it into my code (updated the example in my first post) but this part never gets called, I put a console.log in there just to make sure but it doesn't detect the updateVail
How can I find out if my Apache is the problem? Unfortunately I can't put my project onto a public serverVail
Not 100% sure, but this answer might be useful - https://mcmap.net/q/156222/-options-for-testing-service-workers-via-httpAstaire
Just that I understand SwUpdate right... I go into my app, write "hello world" in any component, ng build --prod my app, copy it to my apache2 and the service worker should refresh it... right?Vail
That's right yes, check the debug log at yourdomain.com/ngsw/state and see if there is anything in the Task queueAstaire
Great news.. It now works with http-server! So my apache must be the problem, but what could it be?Vail
The app gets the new ngsw.json file and theres a new hash for main.js in it but the service worker doesn't load the new version of main.js... On localhost it works fineVail
After completely disabling cache he loads the new main.js but it doens't trigger the update subscriptionVail
I just completely reseted my test system and set up a new apache server.. After applying your fix in the main.ts everything works now, thanks for helping me outVail
Service worker is not registered sometimes only because you have setInterval and app is never stable from the beginning because it is triggering ngZOne change detection. To avoid this run all intervals outside angular.Weirdo
ERROR in ..: error TS2304: Cannot find name 'interval'.Dearden
where is navigator defined.Dearden
window.navigator @DeardenCataclysmic
@VytautasPranskunas can you elaborate on "run all intervals outside angular"?Cataclysmic
I couldn't make it work. created a new question with the issues i am geting from this code. @Yeswhen you can take a peek too #55798111Dearden
I am having the same issue. Can you tell me what did you do to fix this? Do I need to disable caching on server? It works for me on http-server but when I host it on azure box the available event is never triggered.Elyse
Yes, the server should not cache ngsw-worker.js. Did you register the service worker in main.ts like in the answer above? You can try register it by yourself in the console by writing navigator.serviceWorker.register('ngsw-worker.js'), maybe the path is different in your azure box @ElyseCataclysmic
@MichaelDoye can you please tell me the proper testing scenario(basically how can I verify that it's working properly or not?) for service worker in local mode.Effector
@BhaumikThakkar please see the docs here: angular.io/guide/…Astaire
@MichaelDoye Is this solution still valid and recommended for Angular V8? I notice after an update my PWA in IOS did not reload the page, meaning it probably didn't trigger the activateUpdate()Acetous
It worked for me. I did same as @MichaelDoye has suggested.Desalinate
I dont understand this solution to be honest. Why the setInterval? Could you be a bit more explanatory in your answer? For me it works as it should at the time of writing but are having other problems so I do like the answer but don't understand the flow.Constipation
@JimmyKane the interval is there because we need to poll the server to check for updates, this is described in the documentation (although their implementation has changed a bit)Astaire
Hi @MichaelDoye, I've tried your solution and it works fine in my case, but is there any alternative where if it check for an update then found one, that updates.checkForUpdate method will be stopped? because it is calling every 20 secs and I dont want to overload the server call. Thanks.Bickford
@AlvinYanson change the line with ` interval(6 * 60 * 60)` to a normal interval declaration with the function inside and then clear it when the update happens. Or if you are referring to the update subscription you can unsubscribe on update aswell.Hippocras
E
11

I found the following info in the angular docs: https://angular.io/guide/service-worker-devops#debugging-the-angular-service-worker

Summary: There's a useful endpoint on angular sites that shows the service worker state:

http://site-url/ngsw/state

Append /ngsw/state to your sites address. If there is something wrong with the service it should show there.

Example answered 30/8, 2020 at 6:2 Comment(0)
I
8

After every other solution on the stack didn't work, I've started debugging angular's ngsw-worker.js script. I've traced the updating logic and ended up in the "PrefetchAssetGroup" method. I've inserted logging every time method got called and then I realized that it is constantly trying to cache my favicon.ico. I've checked the location of the favicon.ico in my ngsw.json and realized that favicon is not located there. Once I've placed the icon on that location everything started working fine.

Infeasible answered 22/6, 2019 at 6:52 Comment(0)
M
0

Making changes to your application you need to this method activateUpdate(

import { SwUpdate } from '@angular/service-worker';

export class AppComponent implements OnInit {
  constructor(private swUpdate: SwUpdate) { }
  ngOninit() {
    this.swUpdate.available.subscribe(event => {
      console.log('A newer version is now available. Refresh the page now to update the cache');
    });
    this.swUpdate.checkForUpdate()
  }
}

Check this Link:https://angular.io/guide/service-worker-getting-started

Malnourished answered 21/6, 2018 at 12:55 Comment(7)
I did as you said (kept an empty tab open and restarted the http-server after making changes) but the service worker didn't update to the new version, I had to completely close the browser and start it again to get latest version. Is there maybe something wrong with my ngsw-config.json?Vail
Still doesn't work, the SwUpdate.available.subscribe never gets triggered but I don't know why. I put a console.log in it but I can never see it. If I build my app and place it on my server the service worker doesn't react to it at allVail
sorry can you add this._swUpdate.checkForUpdate() inside your if condtionWeissberg
I'm using an interval which checks for updates every 10sec but it doesn't react to the new version (I updated my code sample)Vail
I'm not sure if I'm allowed to share the whole code because it's an internal project. Which files could I provide to help?Vail
did you added this.swUpdate.checkForUpdate() inside your ngOnInit lifecycle hook?Weissberg
I did but it didn't change anything. If it runs the checkForUpdate() method I can see that it request 3 files in the network tab "ngsw.json?ngws-cache-burst=...., config.php, config.php?ngsw-cache-burst=..." I don't know why my config.php is in there but that's all what happensVail
A
0

I've been using the solution in the accepted answer from Michael Doye for some time, but it stopped working when I upgraded to Angular 14.

The solution below is derived from comment by Vytautas Pranskunas

Run interval outside Angular using NgZone

    constructor(public swUpdate: SwUpdate, private ngZone: NgZone) {

    if (swUpdate.isEnabled) {
      this.ngZone.runOutsideAngular(() =>
        interval(1000 * 10).subscribe(val => {
          swUpdate
            .checkForUpdate()
            .then(_ => console.log('SW Checking for updates'));
        })
      );
    }

(Checking every 10 seconds for test purposes - increase the interval for production)

Accentuate answered 13/7, 2022 at 19:42 Comment(0)
M
0

I wrote this code for trigger new version Maybe helpful for you,

ngOnInit(): void {
    this.updates.versionUpdates
      .pipe(
        filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
        switchMap(() =>
          this.snackBar
            .open('New version available', 'Update now')
            .afterDismissed()
        ),
        filter((result) => result.dismissedByAction),
        map(() => this.updates.activateUpdate().then(() => location.reload()))
      )
      .subscribe();
  }
Murmurous answered 6/1, 2024 at 18:46 Comment(0)
A
-1

This worked for me on all devices mac/windows/ios/android

export class PwaUpdateService {

    updateSubscription;

    constructor(public updates: SwUpdate) {
    }

    public checkForUpdates(): void {
        this.updateSubscription = this.updates.available.subscribe(event => this.promptUser());

        if (this.updates.isEnabled) {
            // Required to enable updates on Windows and ios.
            this.updates.activateUpdate();

            interval(60 * 60 * 1000).subscribe(() => {
                this.updates.checkForUpdate().then(() => {
                    // console.log('checking for updates');
                });
            });

        }

        // Important: on Safari (ios) Heroku doesn't auto redirect links to their https which allows the installation of the pwa like usual
        // but it deactivates the swUpdate. So make sure to open your pwa on safari like so: https://example.com then (install/add to home)
    }

    promptUser(): void {
        this.updates.activateUpdate().then(() => {
            window.location.reload();
        });
    }
}
Avon answered 6/12, 2019 at 2:47 Comment(0)
V
-4

To check if newer a version of Angular is available, you can call below method from any component, or just copy the IsNewerVersionAvailable method in app.component.

export class DataService {
    
    constructor(private http: HttpClient, private swUpdate: SwUpdate) { }

    private available: boolean = false;
 
    IsNewerVersionAvailable() {
        if (this.swUpdate.isEnabled) {
            this.swUpdate.available.subscribe(() => {
                this.available = true;
            });
            console.log(this.available);
        }
        return this.available;
    }    
}
Vetavetch answered 6/3, 2020 at 20:32 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.