Angular Service Worker Guide
Angular Service Worker Guide
In this post, we will cover how we can configure the Angular CLI build
pipeline to generate applications that in production mode are
downloadable and installable, just like native apps.
We will also add an App Manifest to our PWA, and make the application
one-click installable.
We will also see in detail what the CLI is doing so that you can also add the
Service Worker to an already existing application if needed.
Along the way, we will learn about the Angular Service Worker design
and how it works under the hood, and see how it works in a way that is
quite different than other build-time generated service workers.
Table Of Contents
In this post, we will cover the following topics:
Summary
This post is part of the ongoing Angular PWA Series, here is the
complete series:
So without further ado, let's get started turning our Angular Application
into a PWA!
If you want to try the latest features, it's also possible to get the next
upcoming version:
And with this in place, we can now scaffold an Angular application and
add it Angular Service Worker support:
1 "apps": [
2 {
3 "root": "src",
4 "outDir": "dist",
5 "assets": [
6 "assets",
7 "favicon.ico"
8 ],
9 "index": "index.html",
10 "main": "main.ts",
11 "polyfills": "polyfills.ts",
12 "test": "test.ts",
13 "tsconfig": "tsconfig.app.json",
14 "testTsconfig": "tsconfig.spec.json",
15 "prefix": "app",
16 "styles": [
17 "styles.css"
18 ],
19 "scripts": [],
20 "environmentSource": "environments/environment.ts",
21 "environments": {
22 "dev": "environments/environment.ts",
23 "prod": "environments/environment.prod.ts"
24 },
25 "serviceWorker": true
26 }
27 ]
We will cover these two files in detail, right now let's see what else the
CLI has added for PWA support.
1
2 @NgModule({
3 declarations: [
4 AppComponent
5 ],
6 imports: [
7 BrowserModule,
8 AppRoutingModule,
9 ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.
10 ],
11 providers: [],
12 bootstrap: [AppComponent]
13 })
14 export class AppModule {}
15
More than that, this module registers the Angular Service Worker in the
browser (if Service Worker support is available), by loading the ngsw-
worker.js script in the user's browser via a call to
navigator.serviceWorker.register() .
behavior, and the generated file comes with some intelligent defaults.
Depending on your application, you might not even have to edit this file!
1 {
2 "index": "/index.html",
3 "assetGroups": [{
4 "name": "app",
5 "installMode": "prefetch",
6 "resources": {
7 "files": [
8 "/favicon.ico",
9 "/index.html"
10 ],
11 "versionedFiles": [
12 "/*.bundle.css",
13 "/*.bundle.js",
14 "/*.chunk.js"
15 ]
16 }
17 }, {
18 "name": "assets",
19 "installMode": "lazy",
20 "updateMode": "prefetch",
21 "resources": {
22 "files": [
23 "/assets/**"
24 ]
25 }
26 }]
27 }
There is a lot going on here, so let's break it down step-by-step. This file
contains the default caching behavior or the Angular Service Worker,
which targets the application static asset files: the index.html , the CSS
and Javascript bundles.
one entry named app , for all single page application files (all the
application index.html, CSS and Javascript bundles plus the favicon)
another entry named assets , for any other assets that are also
shipped in the dist folder, such as for example images, but that are
not necessarily necessary to run every page
The Service worker will not wait for these files to be requested by the
application, instead, it will download them ahead of time and cache
them so that it can serve them the next time that they are requested.
This is a good strategy to adopt for the files that together make the
application itself (the index.html, CSS and Javascript bundles) because
we already know that we will need them all the time.
Again this is a great strategy for any assets that get downloaded in a
separate HTTP request such as images because they might not always
be needed depending on the pages the user visits.
But if they were needed once then its likely that we will need the
updated version as well, so we might as well download the new version
ahead of time.
Again these are the defaults, but we can adapt this to suit our own
application. In the specific case of the app files though, it's unlikely that
we would like to use another strategy.
After all, the app caching configuration is the download and installation
feature itself that we are looking for. Maybe we use other files, outside
the bundles produced by the CLI? In that case, we would want to adapt
our configuration.
It's important to keep in mind that with these defaults, we already have a
downloadable and installable application ready to go, so let's try it out!
1
2 <h1>Version V1 is runnning ...</h1>
3
Let's now build this Hello world PWA app. The Angular Service Worker
will only be available in production mode, so let's first do a production
build of our application:
ng build --prod
This will take a moment, but after a while, we will have our application
build available inside the dist folder.
It's the ServiceWorkerModule that will trigger the loading of this file
indirectly, by calling navigation.serviceWorker.register() .
Note that the Angular Service Worker file ngsw-worker.js will always be
the same with each build, as it gets copied by the CLI straight from
node_modules .
This file will then remain the same until you upgrade to a new Angular
version that contains a new version of the Angular Service Worker.
1 {
2 "configVersion": 1,
3 "index": "/index.html",
4 "assetGroups": [
5 {
6 "name": "app",
7 "installMode": "prefetch",
8 "updateMode": "prefetch",
9 "urls": [
10 "/favicon.ico",
11 "/index.html",
12 "/inline.5646543f86fbfdc19b11.bundle.js",
13 "/main.3bb4e08c826e33bb0fca.bundle.js",
14 "/polyfills.55440df0c9305462dd41.bundle.js",
15 "/styles.1862c2c45c11dc3dbcf3.bundle.css"
16 ],
17 "patterns": []
18 },
19 {
20 "name": "assets",
21 "installMode": "lazy",
22 "updateMode": "prefetch",
23 "urls": [],
24 "patterns": []
25 }
26 ],
27 "dataGroups": [],
28 "hashTable": {
29 "/inline.5646543f86fbfdc19b11.bundle.js": "1028ce05cb8393bd53706064e3a8
30 "/main.3bb4e08c826e33bb0fca.bundle.js": "ae15cc3875440d0185b46b4b73bfa7
31 "/polyfills.55440df0c9305462dd41.bundle.js": "c3b13e2980f9515f4726fd262
32 "/styles.1862c2c45c11dc3dbcf3.bundle.css": "3318b88e1200c77a5ff691c03ca
33 "/favicon.ico": "84161b857f5c547e3699ddfbffc6d8d737542e01",
34 "/index.html": "cfdca0ab1cec8379bbbf8ce4af2eaa295a3f3827"
35 }
36 }
Note that each asset will have a hash entry in the hash table. If we do
any modification to any of the files listed there (even if its only one
character), we will have a completely different hash in the following
Angular CLI build.
The Angular Service Worker will then know that this file has a new
version available on the server that needs to be loaded at the
appropriate time.
Now that we have a good overview of everything that is going on, let's
see this in action!
cd dist
http-server -c-1 .
The -c-1 option will disable server caching, and a server will normally
be running on port 8080 , serving the production version of the
application.
Note that if you had port 8080 blocked, the application might be running
on 8081 , 8082 , etc., the port used is logged in the console at startup
time.
If you have a REST API running locally on another server for example in
port 9000, you can also proxy any REST API calls to it with the following
command:
These files can all be found in Cache Storage, using the Chrome Dev
Tools:
The Angular Service Worker will start serving the application files the
next time you load the page. Try to hit refresh, you will likely notice that
the application starts much faster.
Let's now hit refresh after shutting down the http-server process: you
might be surprised that the application is still running, we get the exact
same screen!
It looks like all the Javascript and CSS bundle files that make the
application where fetched from somewhere else other than the network
because the application is still running.
The only file that was attempted to be fetched from the network was the
Service Worker file itself, which is expected (more on this in a moment).
Let's say that we made a small change to the application, like for
example editing a global style in the styles.css file. Before running the
production build again, let's keep the previous version of ngsw.json , so
that we can see what changed.
Let's now run the production build again, and compare the generated
ngsw.json file:
As we can see, the only thing that changed in the build output was the
CSS bundle, all the remaining files are unchanged except for
index.html (where the new bundle is being loaded).
In our case, the previous and the new versions of the ngsw.json file will
be compared, and the new CSS bundle will be downloaded and installed
in the background.
The next time the user reloads the page, the new application version will
be shown!
For that it's possible to show a dialog to the user and ask if he wants to
reload, using the SwUpdate service and the checkForUpdate() method.
1 @Component({
2 selector: 'app-root',
3 templateUrl: './app.component.html',
4 styleUrls: ['./app.component.css']
5 })
6 export class AppComponent implements OnInit {
7
8 constructor(private swUpdate: SwUpdate) {
9 }
10
11 ngOnInit() {
12
13 // check back to the server to see if a new ngsw.json is available
14 // this can be called inside setInterval, upon router navigation, e
15 this.swUpdate.checkForUpdate();
16
17 this.swUpdate.available.subscribe(() => {
22 }
23 }
In this case, the first time that the user loads the application after the
deployment of the new version, it will see a dialog box with the message:
If the user clicks OK, the application will then be reloaded and the new
version will be shown! Note that normally we don't need to call
checkForUpdate() at application startup, as that is done automatically.
The last piece that we are now missing for a complete one-click
installation experience, is to ask the user to install the application to its
Home Screen.
On the other hand, in order for the App Manifest to work, we need a
Service Worker running on the page! By providing a standard
manifest.json file, the user will be asked to install the application to
Also, the option for installing to Home Screen will only be shown if
certain extra conditions are met.
1 {
2 "dir": "ltr",
3 "lang": "en",
7 "start_url": "http://localhost:8080/",
12 "background_color": "transparent",
13 "related_applications": [],
14 "prefer_related_applications": false,
15 "icons": [
16 {
17 "src": "/favicon.ico",
19 },
20 {
21 "src": "/assets/android-icon-36x36.png",
22 "sizes": "36x36",
23 "type": "image/png",
24 "density": "0.75"
25 },
26 {
27 "src": "/assets/android-icon-48x48.png",
28 "sizes": "48x48",
29 "type": "image.png",
30 "density": "1.0"
31 },
32 {
33 "src": "/assets/android-icon-72x72.png",
34 "sizes": "72x72",
35 "type": "image/png",
36 "density": "1.5"
37 },
38 {
39 "src": "/assets/android-icon-96x96.png",
40 "sizes": "96x96",
41 "type": "image/png",
42 "density": "2.0"
43 },
44 {
45 "src": "/assets/android-icon-144x144.png",
46 "sizes": "144x144",
47 "type": "image/png",
48 "density": "3.0"
49 },
50 {
51 "src": "/assets/android-icon-192x192.png",
52 "sizes": "192x192",
53 "type": "image/png",
54 "density": "4.0"
55 }
56 ]
57 }
This file defines what the icon installed to the Home screen will look like,
and it also defines a couple of other native UI parameters.
1
2 "apps": [
3 {
4 "root": "src",
5 "outDir": "dist",
6 "assets": [
7 "assets",
8 "manifest.json",
9 "favicon.ico"
10 ],
But we can trigger the button with the Chrome Dev Tools, in the Manifest
tab using the Add To Home Screen option:
As we can see, on a Mac the look and feel of the button is still in early
stages, but this is what the button should look like on mobile:
And with this in place, we now have a complete one-click download and
installation experience for our application.
Summary
Native-like Application Download and Installation is now simpler than
ever with the Angular Service Worker and the Angular CLI!
Any application (mobile or not) can benefit from a much faster startup
time, and this feature can be made to work out of the box with some
intelligent defaults provided by the Angular CLI.
I hope that this post helps with getting started with the Angular Service
Worker and that you enjoyed it! If you want to learn more about other
Angular PWA features, have a look at the other posts of the complete
series: