A couple of weeks ago I attended Angular Conf Australia and did a full-day workshop on Progressive Web Applications. In this post I’ll talk about what I learned. In doing so, I’ll show the basic concepts of progressive web applications and service workers, and demonstrate how service workers work in a real application.

 What’s a Progressive Web Application?

The term Progressive Web Application (PWA) was coined by designer Frances Berriman and Google Chrome engineer Alex Russell in 2015. It describes web applications that take advantage of new features of modern browsers, such as Service Workers and Web app manifests.
PWAs leverage these features so that web applications can act more like native applications. Alex has explained the benefits of PWAs, what they looks like, and what he thinks the future of PWAs will be in this great post.

Why build a Progressive Web Application?

There are many reasons to build a Progressive Web Application instead of a traditional web application, the most common being:
  • PWAs allow users to use a web application offline
  • PWAs make web pages load faster by using a cache
  • PWAs allow the app to use push notifications
  • PWAs are device agnostic
  • PWAs provide better SEO than a native application
I’ll focus on implementing caching and offline functionality by utilising Service Workers.

Service Workers

A Service Worker is a script that runs separately from the main browser thread. It can act as a network proxy between a web browser and a server. This allows an app to intercept and cache responses to network requests, and use the cached responses when the same requests are sent later. This is especially useful if the network is too slow or even offline.
Service Workers also provide a foundation to build more advanced features for web applications to work more like native applications. These include push notifications and background syncing.

PWAs with Angular

Angular ships with a service worker implementation which makes installing, registering and activating Service Workers on web applications very easy.
In this demonstration I’ll set up a PWA with Angular and demonstrate it’s offline capabilities. I will use Angular CLI v6, NodeJS v6.9.5 and npm v5.4.2.
You can also find instruction how to enable service worker support in your CLI project in the Angular docs.

Setting up the Project

Let’s start by setting up a clean Angular project:
ng new angular-pwa
I’ve used Angular CLI here because it makes things so easy and simple. However, you can setup an Angular project whatever way you feel like.
The command will create a set of base files automatically:
CREATE angular-pwa/README.md (1027 bytes)
CREATE angular-pwa/angular.json (3593 bytes)
CREATE angular-pwa/package.json (1315 bytes)
CREATE angular-pwa/tsconfig.json (384 bytes)
CREATE angular-pwa/tslint.json (2805 bytes)
CREATE angular-pwa/.editorconfig (245 bytes)
CREATE angular-pwa/.gitignore (503 bytes)
CREATE angular-pwa/src/environments/environment.prod.ts (51 bytes)
CREATE angular-pwa/src/environments/environment.ts (631 bytes)
CREATE angular-pwa/src/favicon.ico (5430 bytes)
CREATE angular-pwa/src/index.html (297 bytes)
CREATE angular-pwa/src/main.ts (370 bytes)
CREATE angular-pwa/src/polyfills.ts (3194 bytes)
CREATE angular-pwa/src/test.ts (642 bytes)
CREATE angular-pwa/src/assets/.gitkeep (0 bytes)
CREATE angular-pwa/src/styles.css (80 bytes)
CREATE angular-pwa/src/browserslist (375 bytes)
CREATE angular-pwa/src/karma.conf.js (964 bytes)
CREATE angular-pwa/src/tsconfig.app.json (194 bytes)
CREATE angular-pwa/src/tsconfig.spec.json (282 bytes)
CREATE angular-pwa/src/tslint.json (314 bytes)
CREATE angular-pwa/src/app/app.module.ts (314 bytes)
CREATE angular-pwa/src/app/app.component.css (0 bytes)
CREATE angular-pwa/src/app/app.component.html (1141 bytes)
CREATE angular-pwa/src/app/app.component.spec.ts (994 bytes)
CREATE angular-pwa/src/app/app.component.ts (207 bytes)
CREATE angular-pwa/e2e/protractor.conf.js (752 bytes)
CREATE angular-pwa/e2e/src/app.e2e-spec.ts (307 bytes)
CREATE angular-pwa/e2e/src/app.po.ts (208 bytes)
CREATE angular-pwa/e2e/tsconfig.e2e.json (213 bytes)

Add Angular PWA

Once the project is set up, you can add the pwa package by running the following command:
ng add @angular/pwa
This will not only add the @angular/service-worker package to your project, but will also do the following jobs for us:
  • Import and register the service worker to the app module
  • Update index.html file and include a manifest.json link and meta tag with the theme-color attribute
  • Add some icon assets to support the installed PWA
  • Create the service worker configuration file ngsw-config.json, which specifies the caching behaviours and other settings

Let’s now take a quick look at some of these files that have been generated.

ngsw-configuration.json

The service worker configuation file, ngsw-config.json, will look something like this:
{
  "index": "/index.html",
  "assetGroups": [{
    "name": "app",
    "installMode": "prefetch",
    "resources": {
      "files": [
        "/favicon.ico",
        "/index.html",
        "/*.css",
        "/*.js"
      ]
    }
  }, {
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {
      "files": [
        "/assets/**"
      ]
    }
  }]
}
It tells the browser to prefetch and install base application index.html, CSS and JS files. It will use lazy install for any other asset files.

manifest.json

The app manifest file ‘manifest.json’ is what tells the browser how the application should behave when installed on devices.
If you’re not using “@angular/pwa”, you’ll need to ensure the theme-color meta tag has been added to every page.
Otherwise, the `theme_color` property can be added to the `manifest.json`.
For example,
{
  "name": "angular-pwa",
  "short_name": "angular-pwa",
  "theme_color": "#1976d2",
  "background_color": "#fafafa",
  "display": "standalone",
  "scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "assets/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}
manifest.json can have more properties than shown above. For example, if you want to ask users to install a home screen icon, the following properties must exist:
  • short_name / name
  • icons (must include 192px and a 512px icons)
  • start_url
  • display (must be either fullscreen, standalone or minimal-ui)
Note that for Chrome version 68 and later, adding those properties won’t automatically trigger a popup to ask to install the home screen button. Instead, the “prompt()” method must be called on the “beforeinstallprompt” event.
Also, it’s worth being away that not all browsers support every property of the manifest.json yet.

Build for production

Service Workers registration will take place only in a production-like environment so the application must be built with the `–prod` option:
ng build --prod
Also, the ‘ng serve’ command, which uses webpack-dev-server, does not work with service workers, because by design it serves source code from memory not from actual file system. Whilst this enables cool features like auto reloading and hot module replacement for regular usage, you’ll need another server to test service workers locally.
For these instructions, I will install the http-server package and then run it:
http-server
Now, when I go to http://localhost:8080, the service worker will be registered and the page resources fetched. To verify this, you can look at the ‘Network’ and ‘Application’ tabs in Chrome DevTools:
http-server
not-cached
Next, you can check the ‘Offline’ option in the Network tab to simulate offline behaviour, then do a page reload:
offline
As you can see, index file, images, css, js files have all been served by the ServiceWorker in offline mode – the app still loads!
This caching works even when you are online and do a page refresh, improving overall application performance.

Update application

If you look at the network tab in the previous screenshot, you’ll notice that a request for “ngsw.json” has been made by “ngsw-worker.js”. This is the service worker checking if there are any changes to the hashes of the assets which had been generated by Angular PWA.
If there are any changes, the service worker will fetch the new application asset files.  If necessary, it will invalidate the whole manifest and download all manifest assets again as well. Then, it will install the new version of the application with the new assets.
For example, if we look at “ngsw.json” for the initial version of the app, we’ll see:
first-hash
Note that the hashes for index.html and main js file are:
/index.html: "f0f463f9c1f1b566a7d2e84a2d7cd635efac5a20"
/main.4cd51701db6a0d4ad1cd.js: "05ee196d167a710c226ae7eaecf3a44594ab7fe0"
If I then updated the title of the application from “Welcome to app!” to “Welcome to PWA”, rebuild it and then reload from http-server, I’ll see the following in the network tab:
update-app
We can see that a new ngsw.json file has been downloaded, followed by index.html and the new JS bundle. If we look at the contents of ngsw.json:

second-hash

We see that the hashes for index.html and main javascript file have changed:
/index.html: "4eb03b8c7153496b4262be68a7e63107e80e4f42"
/main.987365060304b77b181b.js: "a8f855ad4ea2e061cdd7348265d360b06493f2c4"
This is what triggered the service worker to download new versions of the files.
However, at this point the rendered page still shows “Welcome to app!”. This is because service workers always render the installed application before they check and install a new version. However, if you refresh the page, you’ll see:
refresh

Conclusion

Although PWA technologies have been around for a while, they are still under major development. There are inconsistencies between the feature sets implemented by modern browsers. I think this is the main reason why PWAs have not gained massive popularity yet.
However, I do see more and more web applications adopting service workers to improve application performance, and sometime even add offline capabilities. I think this alone helps web applications provide more native-like features, even if those apps aren’t full-blown PWAs.
Through this article, I hope readers have developed a better understanding of the basic concept of PWAs and service workers. In particular, I’ve tried to show how service worker caching can be used right now to make an app both more performant and offline-capable.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s