Getting started with Progressive Web Apps and Angular 5

With the recent release of Angular 5.1 and its CLI in version 1.6, Angular released his new module @angular/service-worker, developed to help us creating Progressive Web Apps. Angular team is saying that it's a first release, and the work will continue in the future, to add new functionalities.
The goal of the article is to build a full progressive web application (front end and back end), using Angular 5.1, and Azure for the back end (Functions + Tables)

What's a Progressive Web App ?

I won't give you a definition of a Progressive Web App, the better thing to do is probably to read the one Google Developers is giving. What's interesting is what does it mean for us developers, when building the app. For that, let's use Lighthouse, the tool in the audit tab of Chrome Developers Tools.

When clicking on "Perform an audit...", choose Progressive Web App, and Lighthouse will evaluate your application with a set of criteria.

  • The web app is using HTTPS and is redirecting from HTTP to HTTPS
  • The web app is responsive
  • The web app has an offline mode
  • The web app registers a Service Worker
  • etc...

This gives us plenty to start.

Angular CLI 1.6 is your friend

Angular CLI 1.6 introduced a new flag, allowing us to add the new @angular/service-worker to our web app.

ng new MyApp --service-worker

A Service Worker is a kind of proxy between your web app and the browser and/or network. It allow things like offline mode or native push notifications. The @angular/service-worker module is here to abstract the communication between your app and the service worker in your browser.

To illustrate the rest of the article, we'll use the repository https://github.com/ludovic-m/pwa-chuck which contains the front end of a functionnal Progressive Web App, and its back end on Azure using Azure Functions and Azure Tables, available here : https://github.com/ludovic-m/PwaServerlessBackend.
The App is very simple. The back end store and sends Chuck Norris facts, and the front end is a progressive web app which support Offline mode, Install, and native Push Notifications. If you want to run the app in your azure subscription, just refer to GitHub.

Managing the offline mode

After creating the web app, you can see a file named ngsw-config.json in the /src folder. This is where you'll have to tell what are the files needed in the cache to serve an offline version of your web app.
There's two main sections: assetGroups and dataGroups.
The first one contains every static asset of the web app. The prefetch value of InstallMode is telling the Service Worker to get all the files when loading the web app, unlike the lazy value which means the files are only retrieved when needed. In order to our app to work offline, we'll choose prefetch.

  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html"
        ],
        "versionedFiles": [
          "/*.bundle.css",
          "/*.bundle.js",
          "/*.chunk.js"
        ]
      }
    }]

The second section, dataGroups, is the list of HTTP requests your web app needs to work offline. That's why, once again, we need to cache the result. The cache can be handled in two ways: freshness or performance. The first one is telling the service worker to always request the remote API first, to check if there's a new version. The second one, performance, loads the cached version first, before checking if there's a new version available.

"dataGroups": [
    {
      "name": "api-chuck",
      "urls": [
        "https://<some_url>/**",
        "https://fonts.googleapis.com/**"
      ],
      "cacheConfig":{
        "strategy":"freshness",
        "maxAge":"3d",
        "timeout":"1m",
        "maxSize":100
      }
    }
  ]

Intall your Web App like a native App

If you want your web app to look like a native app, you have to allow your users to install it like a native app. This is where the manifest.json file comes to rescue. It gives your browser all the info it needs to install the web app. Angular does not provide anything yet to generate this file but it's quite straightforward.

  • Add a file named manifest.json in the src/ folder You can find an example of the file here. It's quite simple to understand and the properties are easily understood. Just make sure that the display property value is standalone. If not, your browser won't ask you if you want to install the app.
  • Add the link of the manifest.json file in the header of the index.html file. Example :
<head>  
  <meta charset="utf-8">
  <meta name="theme-color" content="#3f51b5"/>
  <title>PWA Chuck !</title>
  <base href="/">
  <link rel="manifest" href="manifest.json">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>  
  • Configure Angular to declare the manifest.json as an asset of the web app so it can be served like any other static file. To do so, edit angular-cli.json and update the apps/assets part.
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "chuck"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico",
        "manifest.json",
        ...

That's it, your browser should prompt you (under certain conditions) to install the app.

Native Push Notifications

At the moment, Chrome, Firefox and Edge (in an insider preview of Windows at the time of the article) support Push Notifications. Each one of them uses his own notification backend (Firebase for Chrome, Mozilla Cloud Services for Firefox, and Windows Notification Push Service for Edge).
To configure push notifications, the first thing you have to do is to get a VAPID keys pair. This is going to be used to authenticate the back end of your web app, to the browser of the client.
You can get these here : https://web-push-codelab.glitch.me/.
To subscribe to push notifications in your client app, you first have to use the SwPush component of the @angular/service-worker module. Give him your public VAPID keys, and it will take care of subscribing to push notifications in the service worker of your browser.

  private VAPID_PUB_KEY: string;

  subscribeToPush() {
    this._swPush.requestSubscription({
      serverPublicKey: this.VAPID_PUB_KEY
    }).then(...

The result of the requestSubscription function will give you 3 pieces of informations. You will need those to send notifications to the client.

{
  "endpoint": "https://fcm.googleapis.com/fcm/send/cp2Bq0wqMU0:AP...5AG8lHL6R6l_qljY71RlQ4dDK",
  "expirationTime": null,
  "keys": {
    "p256dh": "BCdAQSRe-ZSFO9rNR2...vgNa24HDdqkyeUiE8p3_H4OFLzjfJCQ0YiehAI=",
    "auth": "BNG...IEHV0CQ=="
  }
}

The value of the endpoint property is the (unique) url used by the back end to send notifications. In the keys object, auth is a user secret encrypted with the VAPID public key, which is then encoded (not encrypted) in the p256dh value.
Once you have that, send the object to your back end, store it, and use it to send notifications. In our example, for the demo back end, I chose to store these values in an Azure Table. It's a NoSQL storage designed for this kind of operations. It's fast and reliable.

On the back end side, chose an existing library to send notifications. It will save you troubles in handling things like encryption. My back end is in C#, I chose to use the Web Push C# library. For more infos, you can have a look directly to the code. It's in the AddFact.cs file.

The notification is just a JSON object. But if you want the browser of the client app to understand it as a notification, you'll have to follow conventions, explained here. You can use the class Notification.cs to build the notification and then serialize it into JSON. It should look like that :

{
    "notification": {
      "title": "Subscription completed",
      "body": "You have successfully subscribed to our great notifications from Chuck Norris",
      "dir": "auto",
      "icon": "https://digitalmacgyver.files.wordpress.com/2012/04/chucknorris_approved.jpg",
      "badge": "https://digitalmacgyver.files.wordpress.com/2012/04/chucknorris_approved.jpg",
      "lang": "en",
      "renotify": true,
      "requireInteraction": false,
      "tag": null,
      "vibrate": [
        300,
        100,
        400
      ],
      "actions": [
        {
          "action": "open",
          "title": "Open"
        }
      ],
      "data": {
        "id_fact": 0,
        "fact": "This the subscription confirmation"
      }
    }
  }

Chrome allows you to add images and actions into notifications. We're getting closer to the experience we get from native app notifications.

What's next

Angular allows you, using Angular Universal, to build an App Shell, which is a precompiled version of the home page. It's useful when JavaScript is not available, and to speed up the initial load of the web app.
Finally, the @angular/service-worker module allow you to be notified when a new version of the app is available.