A new Angular Service Worker — creating automatic progressive web apps. Part 1: theory
Announcement: There is “Part 2: practice” of this article is available.
As a “UIs for the future” engineer I love to experiment with the newest versions of frameworks, libraries, APIs — everything connected with web front-end development. Angular and Progressive Web Apps separately work perfectly for me in that sense, because of constant development, updates, changes (sometimes the breaking ones), but their combination is a just explosive mixture.
Let’s explore the direction which Angular team took in their movement to progressive web apps. Recently released Angular 5 Release Candidate 0 introduces a new Angular Service Worker (NGSW), and this is our main topic.
A bit of history first. The first concepts of Angular PWA were introduced in early 2016 and presented at Google IO’16 under “Mobile Toolkit” name. The corresponding website is still there, but the documentation is hopelessly outdated even for older/current version of NGSW (this is why I don’t give a link to it). As a Google Developer Expert working closely with the Angular team, I do my best to fix this lack of information. I present about Angular Service Worker at the dozens of the conferences including the main Angular event — ng-conf, give private and public workshops and maintain a “community-driven” documentation on the current (beta 16) version of
As it stated in the corresponding pull request, a new service worker is a “conceptual derivative of the existing” one. And this is true. The idea is very similar: we power up our application with a service worker by only providing some JSON configuration instead of writing the code manually. But the implementation, as well as configuration settings, are different. For those who are familiar with NGSW beta 16, the key differences are:
- No integration with Angular CLI yet, but the own mini-CLI included
- Settings file for the service worker itself and the one we create are separated more explicitly now. Actually, we write our own JSON file with any name (we will call it service worker configuration file) and build the one for service worker named
ngsw.jsonusing the CLI I mentioned above (we will call it service worker control file). I find this update very useful: in the current version there is constant confusion with
ngsw-manifest.jsonVS web app manifest, also the idea of merging of auto-generated and manually written manifests is not so obvious.
- There is no plugins concept. At the moment it’s not clear how to extend the functionality.
The key difference with other service worker generators (like Workbox, sw-precache) is the fact, that you do not re-generate the service worker file itself, you only update its control file.
Disclaimer: this is just a first release of Angular Service Worker. Not everything works correctly there, and it’s not very developer friendly yet. In addition, the details of implementation, as well as public APIs, could be changed in the next releases.
For our experiments, let’s take my PWA guinea pig app — PWAtter. It’s the simplest Angular 5 RC0 app powered by trivial NodeJS backend. PWAtter can load tweet feeds and subscribe to push notifications. The code is available on GitHub: https://github.com/webmaxru/pwatter/tree/ngsw/
Angular Service Worker is not integrated with Angular CLI yet, so you will not see the
service-worker package in
node_modules/@angular after scaffolding a new app, let’s install the latest version explicitly:
npm install @angular/service-worker@next --save
What we need from the installed package:
- @angular/service-worker/ngsw-worker.js — the service worker itself. The only non-minimized version is included at the moment. We have to copy this file to our
distfolder and register as a service worker.
- .bin/ngsw-config — NGSW Command-line interface
- ServiceWorkerModule exposed by
@angular/service-worker— for using within Angular client apps to register and communicate with the service worker.
The flow will be following:
In our app:
We register a service worker using any of at least 3 options:
- adding registration script to index.html
- using the same code in
- going “NGSW”-way and using
ServiceWorkerModule, let’s go for this option:
In our build process:
- Build a production version of our app — development build will not work correctly with NGSW
- After all, generate
ngsw.json— a control file for Angular Service Worker (successor of
ngsw-manifest.json) using NGSW CLI
Angular Service Worker Command-line interface is a simple utility, taking a configuration file written by developers, and converting it to
ngsw.json — control file to be used by NGSW.
ngsw-config outputFolder sourceConfigurationFile baseHref
- outputFolder — where to copy the resulting ngsw.json
- sourceConfigurationFile — configuration file we want to process. Let’s have it in
- baseHref — the value we use in
<base href="/">meta tag of index.html. It’s “/” by default and can be skipped
So the command to generate
ngsw.json and to put it to the
dist folder will be:
node_modules/.bin/ngsw-config dist ./src/ngsw-config.json
(if we have our app located in the root folder)
Based on the flow we’ve just described, we can add the following command to the scripts section of
packages.json to get a full build, including all the operations with the service worker:
“build-prod-ngsw”: “ng build -prod && node_modules/.bin/ngsw-config dist ./src/ngsw-config.json && cp node_modules/@angular/service-worker/ngsw-worker.js ./dist/ngsw-worker.js”
Now we have to explore the syntax of
ngsw-config.json — the configuration file for Angular Service Worker.
The interface file from NGSW source code looks like this:
- appData —any application metadata for this specific version. For example build hash, package.json version, release date.
From NGSW configuration design doc:
The appData property allows the application to attach metadata to the configuration, which is not used by the SW but may be delivered as part of update notifications or allow the application to make more intelligent decisions about when to update. For example, a particular update could be marked as containing a security fix and could be applied as soon as possible, but a small bugfix update may be applied on the next reload, without bothering the user with a notification.
- index — path to the
index.htmlfile (where to redirect all the navigation requests)
- assetGroups — named groups of the explicitly known at build time resources to be cached. The most natural application of this setting is specifying the application shell resources.
- dataGroups — named groups of the resources to be cached during runtime, “on demand” when we have to apply one of the caching strategies. The best example here is API calls.
We specify here:
Name of the group. This will be a part of Cache API storage name
Determines when the resources in the group are fetched and cached. There are 2 possible options:
- prefetch — All resources are downloaded when the service worker is setting up caching for this app version. This mode should be used for all the assets required for bootstrapping the application (application shell) if your application aims to be capable of full offline mode.
- lazy — Each resource is cached individually when it’s requested.
Determines how each cached asset behaves when a new version of the application is downloaded. It has same 2 options:
- prefetch — Refresh the asset (if needed) on every new version. For files with hashes (versionedFiles), the asset is downloaded only if the hash has changed. For URLs in the cache, they will be refreshed always (possibly with an If-Modified-Since request)
- lazy — The above flow will be performed only when the resource was requested
The explicit list of resources to cache. There are 3 ways to set them up:
- files —A list of globs matched against files in the configured distribution directory. These files will have their contents hashed and the hashes will be included in the resulting ngsw.json file’s hashTable node, for accurate versioning. The file paths will be mapped into the URL space of the application, starting with the base href.
- versionedFiles — The same, but for the files, which already include a hash in their name. In case of default production Angular 5 app build, these are html, js, css files. There is no need to calculate the hash, because these files have different names if changed, so the service worker flow could be simplified.
- urls — A list of external URLs (either relative, absolute paths, or on different origins) that should be cached. These are often URLs to CDNs or other external resources, for example, the URLs of Google Font API .woff2 files. URLs cannot be hashed, so they are updated whenever the configuration file changes. If these are resources which aren’t precisely known but which still belong in the set of cached assets, we can use globs here. Please note: using the 3rd party, external URLs is not the best practice for the app shell implementing. It’s much better to have full control on pre-cached resources.
We specify here:
Name of the group. This will be a part of Cache API storage name
The same as in assetGroups — A list of glob patterns which match the URLs of requests.
Settings for the defining of caching strategy and fine-tuning this process:
- maxSize — maximum number of responses cached per group
- maxAge — to specify how long the cached response is valid. Could be set as a number of seconds, minutes, hours or days. Like 30m, 2h, 1d.
- timeout — valid for Freshness strategy (see below). The response waiting time after which there will be a fallback to the cache. Set in the same unit as maxAge.
- strategy — two options: “freshness” VS “performance”. Basically, Network-First VS Cache-First caching strategies.
Now we are ready to create our
Out of curiosity, let’s check how the control file will look like after we run our full build command
npm run build-prod-ngsw
Go to the
dist and open
Despite it’s not for us, but for Angular Service Worker, it’s still pretty readable, which is good for us, developers.
After all, we are ready to serve our app using any static web server or deploy it, and check how our service worker actually works. We’ll go for it in the next article about Angular Service Worker.
- Issues of the new Service Worker submitted by me: unstable app shell, freshness strategy doesn’t work, error during push notification. Be aware of these issues while experimenting with NGSW.
- Angular Service Worker Configuration — public draft. Outdated, but contains explanations about some decisions
- NGSW beta.16 unofficial documentation — a full guide on the current version
I would like to express my gratitude and appreciation to Alex Rickabaugh from the Angular team for a great job on implementing Angular Service Worker and a lot of time spent on the answering my questions and reviewing my code.