in the #YearOfPWA

Maxim Salnikov

@webmaxru

Progressive

Angular Apps

How to create Angular Progressive Web App?

Using the native tools

Maxim Salnikov

  • Google Developer Expert in Angular

  • Angular Oslo / PWA Oslo meetups organizer

  • ngVikings /  ngCommunity organizer

Products from the future

UI Engineer at ForgeRock

After all, what is PWA?

Progressive web apps use modern web APIs along with traditional progressive enhancement strategy to create cross-platform web applications.

These apps work everywhere and provide several features that give them the same user experience advantages as native apps.

Cross-platform?

Browser

Desktop

Mobile

Flagged

OS

#YearOfPWA

UX advantages?

Smart networking + Offline

Proper app experience

Staying notified

Other cool things

}

Service Worker API

Web App Manifest

Service worker options

  • Code service worker manually

  • Use framework-agnostic PWA libraries

  • Use Angular Service Worker

NGSW

Automate it!

Automation

  •   Scaffolding

  •   Building

  •   Running

1

2

3

Schematics

Angular CLI

NGSW

PWA MVP

Recipe #1

=

+

Application shell

Web App Manifest

Fast, responsive, mobile-first

Served via HTTPS

$ ng add @angular/pwa

Apply PWA schematics

1

  • Add service worker registration code to the root module

  • Generate default service worker configuration file

  • Generate and link default Web App Manifest

  • Generate default icons set

  • Enable build support in Angular CLI config

$ ng build --prod

Build

ngsw-worker.js

ngsw.json

dist/project-name

safety-worker.js

assets/icons/*.png

manifest.json

worker-basic.min.js

2

Running and auditing

$ ng serve

Static dev webserver

  • serve

  • superstatic

  • lite-server

$ ng serve --prod

Dev Tools / Audit / PWA

Automated app shell actions

  • Generate assets (+ hashes) list

 

2

3

  • Load and cache assets

  • Set up routing

  • Serve assets from the Cache Storage

  • Load and cache the updated assets

  • Inform the application about the update (next recipe)

NGSW configuration file

ngsw-config.json

{
  "index": "/index.html",
  "assetGroups": [...],
  "dataGroups": [...]
}

App shell

assetGroups

{
    "name": "app",
    "installMode": "prefetch",
    "resources": {...}
}

App shell resources

assetGroups / "app" / resources

"resources": {







}
    "files": [
        "/favicon.ico",
        "/index.html",
        "/*.css",
        "/*.js"
    ]

App update notification

Recipe #2

App version updates

v1

v2

v1

v1

v2

Deployed

Displayed

v2

A new version of the app is available. Click to refresh.

Using SwUpdate service

import { SwUpdate } from '@angular/service-worker';
constructor(updates: SwUpdate) {}
this.updates.available.subscribe(event => {




})

updates.component.ts

    if (confirm(`New Version is available! OK to refresh`)) {
            window.location.reload();
    }

Hint: Provide a version description

{
  "appData": {
    "versionMessage": "New version: Push notifications added!"
  },
  ...
}
let message = event.available.appData['versionMessage']
if (confirm(message)) {...}

ngsw-config.json

updates.component.ts

Adding more resources to the App Shell

Recipe #3

App shell / on-demand

assetGroups

{
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {...}
}

App shell / on-demand

assetGroups / "assets" / resources

"resources": {








}
    "files": [
      "/assets/**"
    ],
    "urls": [
        "https://fonts.googleapis.com/**",
        "https://fonts.gstatic.com/**"
    ]

Removing routes from NGSW control

Recipe #4

ngsw-config.json

"navigationUrls": [
  '/**',
  '!/**/*.*',
  '!/**/*__*',
  '!/**/*__*/**',



]
  '!/**/login*',
  '!/**/account*'

What to redirect to index.html

Data requests caching

Recipe #5

Runtime caching

dataGroups

{
    "name": "api-freshness",
    "urls": [
      "/api/breakingnews/**"
    ],






}
    "cacheConfig": {
      "strategy": "freshness",
      "maxSize": 10,
      "maxAge": "12h",
      "timeout": "10s"
    }

Runtime caching

dataGroups

{
    "name": "api-performance",
    "urls": [
      "/api/archive/**"
    ],






}
    "cacheConfig": {
      "strategy": "performance",
      "maxSize": 100,
      "maxAge": "365d"
    }

Hint: Support API versioning

dataGroups

{
    "version": 1,
    "name": "api-performance",
    "urls": [
      "/api/**"
    ],
    ...
}
{
    "version": 2,
    "name": "api-performance",
    "urls": [
      "/api/**"
    ],
    ...
}

Subscribing for the Push notifications

Recipe #6

Push notifications

import { SwPush } from '@angular/service-worker';
constructor(push: SwPush) {}
subscribeToPush() {
  this.push.requestSubscription({
    serverPublicKey: this.VAPID_PUBLIC_KEY
  })
    .then(pushSubscription => {
      // Pass subscription object to the backend
    })
}

push.component.ts

Push notifications / send

{
  "notification": {










  }
}

server-side.js / sendNotification payload

    "title": "Very important notification",
    "body": "Angular Service Worker is cool!",
    "icon": "https://angular.io/assets/logo.png",
    "actions": [
      {
        "action": "gocheck",
        "title": "Go and check"
      }
    ],
    ...

In case of emergency

Recipe #7

Last resort

cp safety-worker.js ngsw-worker.js
self.addEventListener('install', e => { self.skipWaiting(); });

self.addEventListener('activate', e => {
  e.waitUntil(self.clients.claim());
  self.registration.unregister().then(
      () => { console.log('Unregistered old service worker'); });
});

safety-worker.js

A gentle way

rm ngsw.json

Disadvantages

  • Angular Service Worker always works as the main service worker of the whole application

  • There is no [documented] way to extend the functionality

 

Advantages

  • Minimal PWA goes out-of-the-box

  • Essential features are codeless

  • Doing things in Angular way

  • Advanced integrity checks + fallback support

  • 1800+ developers

  • Major browsers/frameworks/libs reps

Thank you!

Maxim Salnikov

@webmaxru

Questions?

Made with Slides.com