34

In previous versions of the Angular service worker implementation, one of the config options was "routing". This can be seen in this unanswered SO question, was referenced in this Angular CLI issue, and the best remaining documentation seems to be the blog post by Stephen Fluin (on the Angular team), as well as the I/O talk from Alex Rickabaugh (from Google).

With Angular 5, the ServiceWorkerModule has been well built-out, and most of the configuration can now be handled using the ngsw-config.json file. However, there is no longer any mention of how to handle redirection for routes, anywhere in the angular.io guide, or in the documentation. So I end up with the following problem: when I have visited my app and go offline, I can still access the app when visiting it directly: https://jackkoppa.github.io/cityaq-sw-issue. However, upon load the app redirects to the search route, and most users would be trying to load from a URL like https://jackkoppa.github.io/cityaq-sw-issue/search?cities=Shanghai (for simplicity, I'm just talking about Chrome desktop & mobile, for now, and in Incognito when possible).

When you try to visit that URL while offline, you immediately get 504 - Gateway Timeout. This is happening because the service worker has only cached the index, and doesn't know that other routes should redirect to the index so that it can load. I'm confident that previous iterations of the Angular service worker implementation could have handled this scenario, by setting up redirects to the index for given routes. Is there a way to handle this redirect in the current, Angular 5+ ngsw-config.json, or in the generated ngsw.json file? Barring that, how should a workaround be handled in a separate service worker JS file?

Jack Koppa
  • 1,193
  • 1
  • 12
  • 26
  • I would really love to know the answer to this question – Stephen Agwu Feb 01 '18 at 15:57
  • @stephenagwu agreed. I haven't been able to find documentation anywhere, which makes me think I may be writing this up as an issue on the Angular repo. Hoping there's someone with a better understanding of service workers before it comes to that, though – Jack Koppa Feb 01 '18 at 16:22
  • 2
    I had opened an issue when I first noticed this behavior. Like you said, it was working fine before v5. However, when it comes to the SW, I'm not sure if the issue is with Angular or with the CLI (which builds the ngsw-worker.js). I've opened it on the Angular repo https://github.com/angular/angular/issues/21636 – MrCroft Feb 04 '18 at 20:26
  • I've created [a repo](https://github.com/jackkoppa/service-worker-test/tree/routing-failure-minimal-repro) that matches the minimal repro steps that @MrCroft found in his issue linked above. Still trying to dig into where `ngsw-worker.js` fails in the fetch process. – Jack Koppa Feb 10 '18 at 21:40
  • Will be letting the bounty expire, since it doesn't feel like we have any full answer on what the Angular team expects. Hopefully I can continue working on a pull request as described in my answer, or someone with more knowledge stumbles upon this – Jack Koppa Feb 12 '18 at 15:41

2 Answers2

11

TL;DR: I've identified at least two issues causing breakage in my case; you can use this build script for now to try my fix, and see the app work offline here. Further testing & pull requests needed.


While I've been unable to find a documented answer, here's what I've been able to find thus far, in stepping through the ngsw-worker.js file, as well as reading the @angular/service-worker commit history.

The new ServiceWorkerModule approach to "routing" is to take any "navigation" request (i.e. a non-index route on the same domain), and re-run the handleFetch method, but now pointing to the index.html request (source). This should work fine, given an SPA should be able to redirect to the URL once it has retrieved its cached files for the index.

So the problems that are causing my site above to fail when offline must not be directly related to routing, and I've found 2 thus far. Correcting these with a workaround has allowed me to get my app working mostly as expected. With the repro steps in the original question, cityaq is now working, while I've reproduced the original, failing site at cityaq-sw-issue.

The issues:

  1. Hosted versions of an Angular app, where the --base-href flag is set from the CLI, list absolute URLs for their service worker resources. When comparing requests to these resources, ngsw-worker.js is expecting relative URLs

    • source
    • I'm fairly confident this issue needs to be resolved, and I'll be working on a pull request to fix it. It happens because the baseHref is getting included in the resource URLs when ngsw.json is generated (source)
  2. Of the 3 available "states" that the ngsw-worker.js can be in, in my experience, EXISTING_CLIENTS_ONLY seems to bet set incorrectly.

    • source 1, source 2
    • I'm less confident about this change, because I haven't been able to consistently see what causes this state. However, if & when the service worker enters this state, ngsw-worker.js will no longer attempt to retrieve cached resources (source), and in my testing under what seem to be "correct" circumstances, the app still enters this state even when the only change is removing an internet connection

While I put together a PR for issue #1, and wait for some help understanding issue #2, I'm using a workaround build script to modify ngsw-worker.js after it's generated. Warning: it's ugly & definitely modifies the intended "safe" behavior of setting EXISTING_CLIENTS_ONLY. However, for my app, it does allow correct offline performance both when running locally (http-server), and deploying to a live URL (GitHub pages, in my case).

To use the workaround: (Angular 5, tested w/ Angular 5.2.1)

  1. npm install replace-in-file --save-dev
  2. Include this script in your app, e.g. at build/fix-sw.js
  3. In your package.json:
"scripts": {
   ...
   "sw-build": "ng build --prod && node build/fix-sw",
   "sw-build-live": ng build --prod --base-href https://your-url.com/ && node build/fix-sw"
   ...
}
  1. To run locally

npm run sw-build
cd dist
http-server -p 8080
  1. And to prepare a build for your live URL, before sending to a server (e.g. with angular-cli-ghpages)

npm run sw-build-live
Jack Koppa
  • 1,193
  • 1
  • 12
  • 26
  • Your solution is working fine. Thank You ! But there is still 504 error in console. Do you have any idea how to handle it ? Or should I wait for this fix https://github.com/angular/angular/pull/22883 to be merged in next Angular release ? – Patryk Panek Mar 23 '18 at 14:30
  • 2
    Yup, I won't be putting in too much effort to resolve the 504 with the build script, until we see how that PR affects the issue. https://github.com/angular/angular/issues/21636 will also be good to watch for any other updates – Jack Koppa Mar 23 '18 at 14:36
  • 3
    Is this fix still required in Angular 6? I've got Angular 6.0.8 and am getting the 504 error in offline mode. – Josh Jobin Jun 28 '18 at 12:37
  • 1
    @JoshJobin - that's a great question, and I'm hoping it's not required. Or at least, if there are still Service Worker issues, they are no longer these 2. I haven't been back on my cityAQ project in a few months, but I created [an issue to upgrade to Angular 6](https://github.com/jackkoppa/cityaq/issues/9), so that I can test out what's working without the build script. Hoping to have an update by August, when I have a bit more time for the project. In the meantime, hopefully someone else will have more recent answers than I do? – Jack Koppa Jul 02 '18 at 23:03
  • @JackKoppa the evidence I've seen says that angular 6 has not addressed this problem. – PeterS Aug 02 '18 at 12:03
  • It was like this is at the end of stack-overflow :)). Thanks for help it seems your fix works locally the second problem is very weird and I think it needs to be addressed multiple times I couldn't reproduce it locally but when I was running on the production it was all over the place, also ng sw docs need to be updated. I would be interested to help fixing the issue you can keep me posted about this or if you don't have time maybe we can discuss the problem and I can help you to put together a pr – Nicu Nov 02 '18 at 07:43
  • Bravo, that works! I had the same issue and surprisingly I'm on Angular 8! However, a weird thing that I've noticed is, the default build is working as expected in Firefox but not on Chrome. Thanks to your solution, it now works on Chrome too! – r0u9hn3ck May 01 '22 at 16:23
0

I just had to deal with this for several days, though I'm not using routes, just a non-root base href. I'll share the configuration that's working for me, which has at least a chance of working for routed apps, because I get my app whenever I try to use route/ngsw/state to debug.

  1. This is a "sub-app" off the main site, and links direct to http(s)://<host>/route/index.html
  2. manifest.webmanifest is just served, so doesn't know or care what angular is doing to things, so it's expliclty including the sub route.
    "scope": "/route/",
    "start_url": "/route/index.html",
    
  3. angular.json has baseHref set, because the whole app is served under this and that's what base-href is for:
    "baseHref": "/route/",
    
  4. ngsw-config.json was the tricky one, because it uses its patterns to find files in the deployment folder to add to ngsw.json, so things like adding baseHref there or using relative paths breaks it. This file needs to think and look like it's being served from the root, e.g.:
    "index": "/index.html",
    ...
      "files": [
        "/index.html",
        "/*.js"
      ]
    
  5. The Angular CLI will add baseHref in ngsw.json, giving you asset URLs and hash keys like "/route/index.html"
  6. Your app will make requests like "/route/index.html" and "/route/main.js" because of the baseHref, which match the keys under which they are hashed and are now actually cached.
  7. The service worker is correctly registered under /route/, so it will a.) attempt to handle requests and b.) find them in its caches when you go offline.

One of the things that made this so damn difficult was how many different ways to specify paths and bases there are, and that some are quite accommodating while others are very strict. If your deployment is broken, you can't actually use the built-in debugging, and you can't just turn on debug logging like with a sane module. The docs are incredibly sparse, especially compared to how well things like Directives and Templates are covered.

Still, this was 1000% easier to get working than any other solution I found.

Side note: If you use ServiceWorkerModule.register('ngsw-worker.js', { enabled: true }) and move "serviceWorker": true, and "ngswConfigPath": "ngsw-config.json" up to the general build options in angular.json instead of under the prod config, you don't have to wait 5 extra minutes for a prod build when debugging.

P.S. Props to OP's sleuthing which got my breakpoints in the right places.

John Neuhaus
  • 1,784
  • 20
  • 32