51

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.

  • Angular docs are the best resource for this, pleaser refer [this page](https://angular.io/guide/service-worker-communications#checking-for-updates) – Pankaj Parkar Jun 04 '20 at 18:32

7 Answers7

59

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));
Community
  • 1
  • 1
Michael Doye
  • 8,063
  • 5
  • 40
  • 56
  • It seems like the promise returned from checkForUpdate() is never fullfilled, the interval works but the 'checking for updates' never appears in my console. –  Jun 21 '18 at 13:45
  • Are you 100% sure the service worker has been properly registered? – Michael Doye Jun 21 '18 at 13:46
  • @MadMurl0c check the updated answer, I modified the `promptUser` method and added something to change in `main.ts` and [here](https://github.com/michaeldoye/Code-Challenge-06-2018/blob/master/src/app/core/utils/worker.service.ts) is an example in a demo project – Michael Doye Jun 21 '18 at 13:55
  • 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? –  Jun 21 '18 at 14:06
  • 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 now –  Jun 21 '18 at 14:32
  • did you see the update to `promptUser` which now activates the update using `this.updates.activateUpdate().then(() => document.location.reload())); ` – Michael Doye Jun 21 '18 at 14:37
  • 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 update –  Jun 21 '18 at 14:41
  • How can I find out if my Apache is the problem? Unfortunately I can't put my project onto a public server –  Jun 21 '18 at 14:52
  • Not 100% sure, but this answer might be useful - https://stackoverflow.com/a/34161385/3055401 – Michael Doye Jun 21 '18 at 14:54
  • 2
    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? –  Jun 21 '18 at 15:25
  • 1
    That's right yes, check the [debug log](https://angular.io/guide/service-worker-devops#locating-and-analyzing-debugging-information) at yourdomain.com/ngsw/state and see if there is anything in the Task queue – Michael Doye Jun 21 '18 at 15:45
  • Great news.. It now works with http-server! So my apache must be the problem, but what could it be? –  Jun 22 '18 at 05:17
  • 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 fine –  Jun 22 '18 at 05:28
  • After completely disabling cache he loads the new main.js but it doens't trigger the update subscription –  Jun 22 '18 at 05:44
  • 2
    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 out –  Jul 19 '18 at 09:17
  • 1
    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. – Vytautas Pranskunas Sep 21 '18 at 09:11
  • ERROR in ..: error TS2304: Cannot find name 'interval'. – ishandutta2007 Apr 22 '19 at 10:01
  • where is navigator defined. – ishandutta2007 Apr 22 '19 at 12:33
  • window.navigator @ishandutta2007 – Kim Apr 24 '19 at 08:08
  • @VytautasPranskunas can you elaborate on "run all intervals outside angular"? – Kim Apr 24 '19 at 08:33
  • 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 https://stackoverflow.com/questions/55797611/tying-to-update-pwa-swupdate-isenabled-is-true-but-not-calling-the-subscribed-m – ishandutta2007 Apr 25 '19 at 05:33
  • 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. – Angad May 07 '19 at 20:32
  • 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 @Angad – Kim May 09 '19 at 09:53
  • @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. – Bhaumik Thakkar Jun 14 '19 at 09:51
  • 1
    @BhaumikThakkar please see the docs here: https://angular.io/guide/service-worker-getting-started#serving-with-http-server – Michael Doye Jun 14 '19 at 11:07
  • 1
    @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() – Abner Aug 20 '19 at 02:27
  • It worked for me. I did same as @MichaelDoye has suggested. – Tahseen Quraishi Oct 30 '19 at 12:37
  • 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. – Jimmy Kane Apr 02 '20 at 17:20
  • @JimmyKane the `interval` is there because we need to poll the server to check for updates, this is described in the [documentation](https://angular.io/guide/service-worker-communications#checking-for-updates) (although their implementation has changed a bit) – Michael Doye Apr 03 '20 at 07:49
  • 1
    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. – alvinydev Sep 21 '20 at 16:41
  • @AlvinYanson change the line with ` interval(6 * 60 * 60)` to a normal [interval](https://www.w3schools.com/jsref/met_win_setinterval.asp) declaration with the function inside and then [clear](https://www.w3schools.com/jsref/met_win_setinterval.asp) it when the update happens. Or if you are referring to the update subscription you can unsubscribe on update aswell. – Aaron Turkel Aug 03 '21 at 12:53
9

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.

Eli
  • 1,670
  • 1
  • 20
  • 23
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.

ivan92
  • 81
  • 1
  • 5
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

Luca Ritossa
  • 1,118
  • 11
  • 22
Chellappan வ
  • 23,645
  • 3
  • 29
  • 60
  • 1
    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? –  Jun 21 '18 at 13:15
  • 1
    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 all –  Jun 21 '18 at 13:58
  • sorry can you add this._swUpdate.checkForUpdate() inside your if condtion – Chellappan வ Jun 21 '18 at 14:05
  • 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) –  Jun 21 '18 at 14:13
  • 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? –  Jun 21 '18 at 14:39
  • did you added this.swUpdate.checkForUpdate() inside your ngOnInit lifecycle hook? – Chellappan வ Jun 21 '18 at 14:44
  • 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 happens –  Jun 21 '18 at 15:06
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)

Alex Cooper
  • 475
  • 3
  • 7
  • 18
-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();
        });
    }
}
Luca Ritossa
  • 1,118
  • 11
  • 22
awkasem
  • 27
  • 1
  • 2
-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;
    }    
}
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42