angular-cli: Existing App does not get Service Worker capabilities

Versions

Angular CLI: 1.6.0
Node: 9.2.0
OS: win32 x64
Angular: 5.1.0
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, platform-server, router
... service-worker

@angular/cli: 1.6.0
@angular-devkit/build-optimizer: 0.0.35
@angular-devkit/core: 0.0.22
@angular-devkit/schematics: 0.0.41
@ngtools/json-schema: 1.1.0
@ngtools/webpack: 1.9.0
@schematics/angular: 0.1.10
@schematics/schematics: 0.0.10
typescript: 2.6.2
webpack: 3.10.0

Repro steps

Have an existing app with no PWA capability and apply steps to upgrade it as described in docs.

Observed behavior

Despite the fact it builds with no error and dist folder has files like ngsw.json & ngsw-worker.js as expected, they are not loaded on network request when browsed. (Tested with incognito mode as well) No service-worker effect at all in app.

Desired behavior

Service Worker added to built app when steps in official docs are applied.

Mention any other details that might be useful

On the same machine, a new app generated with Angular-CLI 1.6.0 with --service-worker flag. That app loads service worker as expected.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 28
  • Comments: 53 (5 by maintainers)

Commits related to this issue

Most upvoted comments

I also had this issue, the culprit seams to be angularFire2. I worked around it by registering the service worker in main.ts.

platformBrowserDynamic().bootstrapModule(AppModule).then(() => {
  if ('serviceWorker' in navigator && environment.production) {
    navigator.serviceWorker.register('/ngsw-worker.js');
  }
}).catch(err => console.log(err));

Service worker won’t run because of ng app is not stable (ApplicationRef.isStable (https://angular.io/api/core/ApplicationRef#members), application is not becoming stable if there is a code which runs intervals (setInteral or Observable.timer and etc. which come from setInterval or setTimeout (related with NgZone.hasPendingMicrotasks)).

As example of such behaviour:

  1. Generate app ng new my-project --service-worker;
  2. First ensure what service-worker works as expected;
  3. Then add to app.component.ts constructor setInterval(t => { console.log('Ping')}, 500);
  4. Try run app again - you will see what service-worker will stops to work

Workaround for such behaviour wrap - wrap all timer-code to this.applicationRef.isStable.subscribe(isStable => { if (isStable) { <code goes here> } })

Full code

import 'rxjs/add/observable/interval';
import { Component, ApplicationRef, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'app';

  constructor(private applicationRef: ApplicationRef) {
  }

  ngOnInit() {

    this.applicationRef.isStable.subscribe((s) => { // #1
      if (s) { // #2
        setInterval(t => { console.log('Ping')}, 500);
      } // #3
    }); // #4
    // If you uncomment 1-4 - service-worker will not run

    this.applicationRef.isStable.subscribe(t => {
      console.log('App stable: ' + t);
    });
  }
}

@alxhub , I am trying to make my existing app into pwa and for that I am following https://angular.io/guide/service-worker-getting-started. Now, the point is I have ejected ng command and hence using webpack. Upon the running the build command, ngsw.json is not getting created in dist folder although, ngsw-worker.js file is getting created. For your review, I am pasting the snippets below.

angular-cli.json code

{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "project": { "name": "core", "ejected": true }, "apps": [ { "root": "src", "outDir": "dist", "assets": [ "assets", "manifest.json", "icons" ], "index": "index.html", "main": "main.ts", "polyfills": "polyfills.ts", "test": "test.ts", "tsconfig": "tsconfig.app.json", "testTsconfig": "tsconfig.spec.json", "prefix": "app", "serviceWorker": true, "styles": [ "styles.css" ], "scripts": [], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" } } ], "e2e": { "protractor": { "config": "./protractor.conf.js" } }, "lint": [ { "project": "src/tsconfig.app.json", "exclude": "**/node_modules/**" }, { "project": "src/tsconfig.spec.json", "exclude": "**/node_modules/**" }, { "project": "e2e/tsconfig.e2e.json", "exclude": "**/node_modules/**" } ], "test": { "karma": { "config": "./karma.conf.js" } }, "defaults": { "styleExt": "css", "component": { } } } Package.json file

{ "name": "kognifai-poseidon-next-core", "version": "0.0.1", "description": "", "main": "index.js", "author": "KDI", "publishConfig": { "registry": "https://kdi.jfrog.io/kdi/api/npm/npm-local/" }, "scripts": { "ng": "ng", "start": "webpack-dev-server --port=4200", "build": "webpack", "test": "karma start ./karma.conf.js", "lint": "ng lint", "e2e": "protractor ./protractor.conf.js", "pree2e": "webdriver-manager update --standalone false --gecko false --quiet", "versionup": "npm-version-up", "pwa": "npm run build --prod && sw-precache --root=dist --config=precache-config.js", "ngsw-config": "node_modules/.bin/ngsw-config dist src/ngsw-config.json", "ngsw-copy": "cp node_modules/@angular/service-worker/ngsw-worker.js dist/", "build-prod-ngsw": "npm run build --prod && npm run ngsw-config && npm run ngsw-copy", "serve-prod-ngsw": "npm run build-prod-ngsw && http-server dist -p 8080" }, "private": false, "dependencies": { "@angular/animations": "^5.0.0", "@angular/common": "^5.0.0", "@angular/compiler": "^5.0.0", "@angular/core": "^5.0.0", "@angular/forms": "^5.0.0", "@angular/http": "^5.0.0", "@angular/platform-browser": "^5.0.0", "@angular/platform-browser-dynamic": "^5.0.0", "@angular/router": "^5.0.0", "@angular/service-worker": "^5.2.3", "core-js": "^2.4.1", "kognifai-design-system": "^0.7.2", "kognifai-poseidon-authenticationservice": "0.1.1", "kognifai-poseidon-settingsservice": "^0.1.2", "lodash": "^4.17.4", "ng-pwa-tools": "0.0.15", "rxjs": "^5.5.2", "uuid": "^3.2.1", "zone.js": "^0.8.14" }, "devDependencies": { "@angular/cli": "^1.5.5", "@angular/compiler-cli": "^5.0.0", "@angular/language-service": "^5.0.0", "@types/jasmine": "~2.5.53", "@types/jasminewd2": "~2.0.2", "@types/lodash": "^4.14.97", "@types/node": "~6.0.60", "autoprefixer": "^6.5.3", "circular-dependency-plugin": "^3.0.0", "codelyzer": "~3.2.0", "copy-webpack-plugin": "^4.1.1", "css-loader": "^0.28.1", "cssnano": "^3.10.0", "exports-loader": "^0.6.3", "file-loader": "^1.1.5", "html-webpack-plugin": "^2.29.0", "istanbul-instrumenter-loader": "^2.0.0", "jasmine-core": "~2.6.2", "jasmine-spec-reporter": "~4.1.0", "karma": "~1.7.0", "karma-chrome-launcher": "~2.1.1", "karma-cli": "~1.0.1", "karma-coverage-istanbul-reporter": "^1.2.1", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", "karma-junit-reporter": "^1.2.0", "karma-phantomjs-launcher": "^1.0.4", "less-loader": "^4.0.5", "npm-version-up": "^0.1.5", "phantomjs-prebuilt": "^2.1.16", "postcss-custom-properties": "^6.2.0", "postcss-loader": "^2.0.8", "postcss-url": "^7.1.2", "protractor": "~5.1.2", "raw-loader": "^0.5.1", "sass-loader": "^6.0.3", "source-map-loader": "^0.2.0", "style-loader": "^0.13.1", "stylus-loader": "^3.0.1", "sw-precache-webpack-plugin": "^0.11.4", "ts-node": "~3.2.0", "tslint": "~5.7.0", "typescript": "~2.4.2", "uglifyjs-webpack-plugin": "1.0.0", "url-loader": "^0.6.2", "webpack": "~3.8.1", "webpack-concat-plugin": "1.4.0", "webpack-dev-server": "~2.9.3" } } manifest.json

`{ “name”: "Poseidon Next, The Complete Solution ", “start_url”: “/”, “orientation”: “any”, “scope”: “/”, “display”: “standalone”,

"short_name": "Poseidon",
"theme_color": "#472D21",
"background_color": "#C0B764",
"icons": [
    {
        "src": "icons/icon_96.png",
        "sizes": "96x96"
    },
    {
        "src": "icons/icon_144.png",
        "sizes": "144x144"
    },
    {
        "src": "icons/icon_192.png",
        "sizes": "192x192"
    },    
    {
        "src": "icons/icon_512.png",
        "sizes": "512x512"
    }
]

}`

ngsw-config.json file

`{ “routing”:{ “index”:“/index.html”, “routes”:{ “/”:{ “match”: “exact” }, “/coffee”:{ “match”: “prefix” }

    }
},
"static.ignore":[
    "^\/icons\/.*$"
],
"external":{
    "urls":[
        {
            "url" : ""
        },
        {
            "url" : ""
        }
    ]
},
"dynamic":{
    "group":[
        {
            "name" : "api",
            "urls" : {
                "http://localhost:8080/#/" :{
                    "match" : "prefix"
                }
            },
            "cache" :{
                "optimizeFor" : "freshness",
                "networkTimeoutMs" : 1000,
                "maxEntries" : 30,
                "strategy" : "lru",
                "maxAgeMs" : 36000000
            }
        }
    ]
},
"push" : {
    "showNotification" : true,
    "backgroundOnly" : true
}

}`

main.ts file

`import { enableProdMode } from ‘@angular/core’; import { platformBrowserDynamic } from ‘@angular/platform-browser-dynamic’;

import { AppModule } from ‘./app/app.module’; import { environment } from ‘./environments/environment’;

if (environment.production) { enableProdMode(); }

platformBrowserDynamic().bootstrapModule(AppModule) // waiting for angular to load bootstrapping and then only loading service worker .then(()=>{ if (‘service-worker’ in navigator){ navigator.serviceWorker.register(‘/ngsw-worker.js’) // navigator.serviceWorker.register(‘/ngsw-worker.js’) } }) .catch(err => console.log(err)); ` app.module.ts file

`import { BrowserModule } from ‘@angular/platform-browser’; import { NgModule } from ‘@angular/core’; import { HttpModule } from ‘@angular/http’; import { FormsModule, ReactiveFormsModule } from ‘@angular/forms’;

import { AppRoutingModule } from ‘./app-routing.module’;

import { AppComponent } from ‘./app.component’; import { LoginComponent } from ‘./login/login.component’; import { HomeComponent } from ‘./home/home.component’; import { AboutComponent } from ‘./about/about.component’; import { NavigationService } from ‘./navigation/navigation.service’; import { HeaderComponent } from ‘./header/header.component’; import { FooterComponent } from ‘./footer/footer.component’; import { SidebarComponent } from ‘./sidebar/sidebar.component’; import { DasboardComponent } from ‘./dasboard/dasboard.component’; import { MainComponent } from ‘./main/main.component’; import { ModuleLoaderService } from ‘./services/module-loader.service’; import { OverlayService } from ‘./services/overlay.service’; import { InitializeService } from ‘./services/initialize.service’; import { CookieService } from ‘./services/cookie.service’; import { SettingsService } from ‘./settings/settings.service’; import { ServiceWorkerModule } from ‘@angular/service-worker’; import { SettingsServiceTestPageComponent } from ‘./settings/test/settingsservice.testpage.component’; import { AuthenticationService } from ‘kognifai-poseidon-authenticationservice/dist/AuthenticationService’; import { AuthenticationServiceTestPageComponent } from ‘./authentication/test/authenticationservice.testpage.component’; import { environment } from ‘…/environments/environment’;

// import { HelloPoseidonModule } from ‘kognifai-poseidon-hello-poseidon’;

@NgModule({ declarations: [ AppComponent, LoginComponent, HomeComponent, AboutComponent, HeaderComponent, FooterComponent, SidebarComponent, MainComponent, DasboardComponent, SettingsServiceTestPageComponent, AuthenticationServiceTestPageComponent ], imports: [ BrowserModule, FormsModule, HttpModule, AppRoutingModule, // ServiceWorkerModule.register(‘/woker-basic.min.js’,{ enabled: environment.production }) ServiceWorkerModule.register(‘/ngsw-worker.js’, {enabled: environment.production}) // HelloPoseidonModule ], providers: [ NavigationService, ModuleLoaderService, SettingsService, AuthenticationService, OverlayService, InitializeService, CookieService ], bootstrap: [AppComponent] }) export class AppModule { } `

Having said that, while running the build command say npm run build-prod-ngsw it gives below error

kognifai-poseidon-next-core@0.0.1 ngsw-config D:\PoseidonNext\Core ngsw-config dist src/ngsw-config.json

(node:23044) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): TypeError: Cannot read property ‘startsWith’ of undefined (node:23044) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

kognifai-poseidon-next-core@0.0.1 ngsw-copy D:\PoseidonNext\Core cp node_modules/@angular/service-worker/ngsw-worker.js dist/

I have also attached screenshots for your review. Kindly, let me know what is wrong going here?

Thanks, Rahul 1st 2nd 3rd 4th

This has definitely not been resolved yet. HNY!

I’ve solved this issue with a workaround, what it does is register the service worker and simulate the messages that ServiceWorkerModule sends. I’ve created a little service with this behaviour, you can check it here: https://github.com/Pedro-vk/EthKudos/blob/abb2104e4338e5b7529e7b50838d9ab787c386fc/src/app/shared/service-worker.service.ts

I had the same problem. In my case it was caused by the default route in the app.module.ts

path: '', redirectTo: '/main/0', pathMatch: 'full'

With these settings the ngsw-worker.js didn’t load.

I encountered also this issue on my existing app. So I tried to generate a new app like so

ng new my-project --service-worker

and after a ng build --prod it did work as expected, the service worker is registered and visible in the dev tools.

Then I tried to generate a new app like so

ng new my-project

I then followed the steps described in the guide and it did also work as expected. So I decided to dig deeper in my own app. After removing some code, I was able to get a service worker running and it appeared that it was due to a conflict with Pusher from the pusher-js package.

@Injectable()
export class SearchService {
  // having this assignment in a service class injected in a component in use at the startup of the app
  // makes the service worker not running (and not visible in the service worker list of the dev tools)
  pusher = new Pusher('key', { cluster: 'eu' })
  // after removing this line and doing an `ng build --prod` the service worker was running as expected.
  ...
}

I have no ideas what could be the root cause of this interaction between this line of code and the service worker as no errors are thrown in the console neither at compile time nor at runtime, but maybe this experience could help solving this issue.

The last thing I did is moving the assignment in a method that is never called at the startup of the app so I could have both a service worker registered and an instance of Pusher running.

Hi, a quick note, updated to angular 6, firebase 5 and angularfire2 5rc10 and the fix is no longer needed, service workers works out of the box.

Hi @alxhub

First, thanks a lot for your help!

I confirm that I build the project through ng build --prod. I then serve the dist folder with http-server as suggested in the guide.

I expect to see the SW registered and activated in the “Application” tab of Chrome devtools, but nothing shows up.

From my terminal: MacBook-Air:testsw sntmrtn$ ng build --prod Date: 2017-12-09T08:08:45.452Z Hash: fe12f6c535c8e1842392 Time: 29354ms chunk {0} polyfills.169c804fcec855447ce7.bundle.js (polyfills) 60.9 kB [initial] [rendered] chunk {1} main.d0d19976182286b68554.bundle.js (main) 171 kB [initial] [rendered] chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes [initial] [rendered] chunk {3} inline.92d63502b90fcb9628dd.bundle.js (inline) 1.45 kB [entry] [rendered] MacBook-Air:testsw sntmrtn$ cd dist/ MacBook-Air:dist sntmrtn$ http-server Starting up http-server, serving ./ Available on: http://127.0.0.1:8080 http://192.168.178.27:8080

And the result: capture

EDIT: It actually works on https, see: https://dist-zobmrubhnx.now.sh/

However, I still did not manage to make it work on my client’s project.

The same here… I find out that service-worker is not registered after I inject AngularFirestore from angularfire2/firestore to a component or a service.

See https://github.com/angular/angularfire2/issues/1347

This costs me few hours to find out what is not working. My service worker dont register in whatever.

The problem is, service worker only worked on HTTPS(secure content)

I struggled with something similar for a long time, but my problem was different. I used http://0.0.0.0:8000 as my URL, but then the serviceWorker is not present as navigator.serviceWorker.

I just changed it to http://localhost:8000 and everything works. It must be because localhost is considered a secure origin.

Maybe that will help someone.

Apparently there are cases that prevent PWA work properly in Angular, but they are not clearly stated in documents or any errors get returned while building your app. If you are only dealing with Angular’s own packages you are supposed to be fine, when you are using external libraries (even ones developed by Google’s own people, like AngularFire2) chances are it will fail by default, then you search and patch your code to make it work.

So I believe now it’s rather a problem of properly informing erroneous cases & documentation issue than service worker part of Angular itself. Still it didn’t work for me as is.

@rahulsahay19 , can I know what the fix is?

Apparently its an issue with the latest Angular 8 release. https://github.com/angular/angular/issues/31061

thanks @ChokeBilly! I had similar messed up default route redirect.

@zendizmo In my case, my ngsw-config.json file was not on latest schema. When I took the sample config, then it worked later on moved my changes in there!

@odahcam not a problem. Its fixed. Thanks!

@rahulsahay19 Try to post a question @ http://stackoverflow.com

@Argentan : Thanx. Your solution works in my project. 😃

At least, the service worker is registered in Chrome. But I’m still facing a problem with caching. When I’m going offline (service worker tab) and reload the page, the app is loaded (HTML, CSS) , but the Google icon font is missing. My ngsw-config.json: "urls": [ "https://fonts.googleapis.com/icon?family=Material+Icons" ]

On a second page reload, the page is empty and the console has no log entry. What is going on? Never thought that it is so hard to get a service worker running and caching. 😉

@sntmrtn thanks for the reproduction!

Can you confirm exactly what steps you’re taking to build and serve the project, where you would expect to see the Service Worker?

As mentioned in the documentation, ng serve --prod will not cause a Service Worker to be registered, only a true ng build --prod will run the SW build step and produce a dist directory with the Service Worker configured. When I build and serve this app (via ng build --prod and http-server), the SW is registered and activated as expected.

@alxhub Please see the following repo:

https://github.com/sntmrtn/testsw