Progressive Web Apps

using the Angular Mobile Toolkit

Maxim Salnikov

Angular GDE

Automatic

Maxim Salnikov

  • Google Developer Expert in Angular

  • Angular Oslo meetup organizer

  • ngVikings conference organizer

Products from the future

UI Engineer at ForgeRock

Milestones of the web

AJAX

Static

Dynamic

RWD

PWA

Progressive Web App

... Progressive Web App can be seen as an evolving hybrid of regular web pages (or websites) and a mobile application

... a new software development methodology

10 characteristics

  • Progressive
  • Discoverable
  • Linkable
  • App-like
  • Responsive
  • Connectivity-independent
  • Re-engageable
  • Installable
  • Fresh
  • Safe

 App Shell architecture

 Service Worker API

Push API and Notifications API

Involved APIs

  • Service Worker API

  • Cache API

  • Fetch API

  • Notifications API

  • Push API

  • IndexedDB API

  • Promises

Mobile Toolkit

Where is --mobile?

The answer

No more

flag

--mobile

2   packages

@angular/service-worker

v1.0.0-beta.8 - Experimental service worker by the Angular Mobile team

@angular/app-shell

v0.1.0 - App Shell runtime library for Angular Progressive Web Apps

Service Worker

Angular Service Worker

NGSW

4   main parts

@angular/service-worker/bundles

@angular/service-worker/build

@angular/service-worker/companion

@angular/service-worker/worker

@angular/service-worker/plugins

NGSW installation

# First, install the Angular Service Worker
$ npm install --save @angular/service-worker
# Enable the SW registration + app shell in Angular CLI
$ ng set apps.0.serviceWorker=true
"apps": [
    {
        "serviceWorker": true
    }
]

.angular-cli.json

App build

# NGSW works only in --prod mode
$ ng build --prod
<script src="sw-register.{HASH}.bundle.js"></script>
  • sw-register.{HASH}.bundle.js

  • worker-basic.min.js

  • ngsw-manifest.json

index.html

Static content caching

{
  "static": {
    "urls": {
      "/index.html": "ae543...",
      "/main.bundle.js": "9ff18...",
      "/styles.bundle.css": "d6f44...",
      "/assets/images/logo.png": "0e33a...",
      ...
    }
  }
}

ngsw-manifest.json

Route redirection

{
  "routing": {
    "index": "/index.html",
    "routes": {
      "/": {
        "prefix": false
      },
      "/some/routes-group-prefix": {
        "prefix": true
      }
    }
  }
}

ngsw-manifest.json

Extending NGSW manifest

{
    "static" : {
        ...autogenerated
    }
}
{
    "routing" : {...}
    ...
}

ngsw-manifest.json

Angular PWA

Static Content

Cache

External content caching

{
  "external": {
    "urls": [
      {
        "url": "https://fonts.gstatic.com/Roboto.ttf"
      }
    ]
  }
}

ngsw-manifest.json

Angular PWA

Static Content

Cache

External Content Cache

Dynamic content caching

{
  "dynamic": {
    "groups": [
      {
        "name": "api",
        "urls": {...},
        "cache": {...}
       }
    ]
  }
}

ngsw-manifest.json

Dynamic cache settings

cache: {
  "optimizeFor": "freshness",   // or 'performance'
  "networkTimeoutMs": 200,
  "maxAgeMs": 1000,
  "maxEntries": 2,
  "strategy": "fifo"            // or 'lru', 'lfu'
}
urls: {
    "/api/breakingnews": {
        "match": "prefix"
    }
},

ngsw-manifest.json

Angular PWA

Static Content

Cache

External Content Cache

Dynamic Content

Cache

Companion

import {NgServiceWorker} from '@angular/service-worker';
constructor(public sw: NgServiceWorker) {}

app.component.ts

import {ServiceWorkerModule} from '@angular/service-worker'
imports: [
    ...
    ServiceWorkerModule
]

app.module.ts

Gentle app update flow

sw.updates.subscribe(event => {
  if (event.type === 'pending') {
    // Ask user if they want to update?
    if (agreeToUpdate) {
        sw.activateUpdate(event.version);
    }
  } else {
    // event.type === 'activation'
    // NGSW is now serving a new version
    location.reload();
  }
});

app.component.ts

Push notifications

Backend

Notification

Push Service

NGSW

App

Backend

Push Service

App

Subscription

Sending a notification

Push settings

{
  "push": {
    "showNotifications": true,
    "backgroundOnly": false
  }
}

ngsw-manifest.json

Push subscription

constructor(public sw: NgServiceWorker) {}
sw.registerForPush().subscribe(subscriptionObject => {
    // Send subscription data to the server
});
import {NgServiceWorker} from '@angular/service-worker';

push.component.ts

sw.push.subscribe(notificationPayload => {
    // Process notification data
});

Angular PWA

Static Content

Cache

External Content Cache

Push

Dynamic Content

Cache

Basic Service Worker

import {bootstrapServiceWorker} from '@angular/service-worker/worker';
import {StaticContentCache} from '@angular/service-worker/plugins/static';
...

bootstrapServiceWorker({
  manifestUrl: '/ngsw-manifest.json',
  plugins: [
    StaticContentCache(),
    ...
  ]
});

worker/builds/basic.ts

Extending functionality

import {bootstrapServiceWorker} from '@angular/service-worker/worker';
import {StaticContentCache} from '@angular/service-worker/plugins/static';
...
import {MyCustomPlugin} from './plugins/my-custom-plugin';

bootstrapServiceWorker({
  manifestUrl: '/ngsw-manifest.json',
  plugins: [
    StaticContentCache(),
    ...
    MyCustomPlugin()
  ]
});

worker-custom.js

Automatic PWA

{
  "static": {...},
  "routing": {...},
  "external": {...},
  "dynamic": {...},
  "push": {...}
}

ngsw-manifest.json

Angular PWA

Static Content

Cache

External Content Cache

Push

Dynamic Content

Cache

Automatically

Thank you!

@webmaxru

Maxim Salnikov

Automatic Progressive Web Apps using the Angular Mobile Toolkit

By Maxim Salnikov

Automatic Progressive Web Apps using the Angular Mobile Toolkit

Progressive Web Apps are the next big thing for the web. They combine the advantages of two platforms: searchability and shareability of the web with capabilities and performance of native mobile. As a result, web developers can use their favorite tools to build installable, re-engageable, connectivity independent apps, that can bring native-like performance and user experience.

  • 1,668