Which option will work better for you

Maxim Salnikov

Angular GDE

Creating Angular PWA

How to create Angular Progressive Web App?

And enjoy doing this

Maxim Salnikov

  • Google Developer Expert in Angular

  • Angular Oslo / PWA Oslo meetups organizer

  • ngVikings conference organizer

Products from the future

UI Engineer at ForgeRock

  • All sides of Angular ecosystem

  • 100% community-driven event from developers for developers

  • True Nordics spirit and Vikings power

March 1-2, Helsinki, Finland

ngVikings.org

Progressive Web App

... attempts to combine features offered by most modern browsers with the benefits of mobile experience

... web apps that use the latest web technologies.

10 characteristics

  • Progressive

  • Discoverable

  • Linkable

  • App-like

  • Responsive

  • Connectivity-independent

  • Re-engageable

  • Installable

  • Fresh

  • Safe

Service worker

Know your toolset

  • Service Worker API

  • Cache API

  • IndexedDB

  • Fetch

  • Clients API

  • Broadcast Channel API

  • Push API

  • Notifications API

  • Local Storage

  • Session Storage

  • XMLHttpRequest

  • DOM access

  • Make some features of the web app function offline

  • Improve online performance by reducing network requests

Service Worker API

  • Receiving push events and displaying notifications

  • Clients (tabs) messaging

  • Job scheduling

  • Responding to resource requests from other origins

Not only networking

Logically

Physically

JS

-file

App

Service worker

In Development

Behind the flag

Frameworks

  • create-react-app

  • preact-cli

  • polymer-cli

  • vue-cli

Create Angular PWA

  • Code service worker manually

  • Use Angular Service Worker (NGSW)

  • Go for some PWA libraries

sw-precache

Service Worker 101

Wait a sec!

  • 800+ developers

  • Major browsers/frameworks/libs reps

App shell

My App

Managing cache

self.addEventListener('install', (event) => {
  
    // Put app's html/js/css to cache

})
self.addEventListener('activate', (event) => {
  
    // Wipe previous version of app files from cache

})

In the real world

  • Can't add opaque responses directly

  • Redirected requests should be managed

  • Always creating a new version of cache and deleting the old one is not optimal

  • Control over cache size is required

  • Cache invalidation for runtime caching is complex

  • ...

Intercepting requests

self.addEventListener('fetch', (event) => {

  if (event.request.url.indexOf('/api') != -1) {
    event.respondWith(
      // Network-First Strategy
    )
  } else {
    event.respondWith(
      // Cache-First Strategy
    )
  }
})

In the real world

  • All kinds of fallbacks needed for the strategies

  • There are more complex strategies like Stale-While-Revalidate

  • Good to have routing

  • Good to have the possibility to provide some extra settings for different resource groups

  • ...

Pros

  • Great flexibility!

 

Cons

  • Great responsibility!

 

  • Implementing complex algorithms

  • Following specifications updates

  • Handling edge cases

  • Adopting best practices

  • Focusing on YOUR task

Tools help with

@angular/service-worker

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

Angular Service Worker

# 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

Configuration

ngsw-manifest.json

{
  "static": {...},
  "routing": {...},
  "external": {...},
  "dynamic": {...},
  "push": {...}
}
$ ng build --prod

Outdated

New NGSW 101

@angular/service-worker

v5.0.0-rc.7

New Angular Service Worker

# Install the New Angular Service Worker
$ npm install --save @angular/service-worker@next

Key differences from b.16

  • No integration with Angular CLI yet

  • Explicit separation of configuration file and control file

  • No plugins

NGSW CLI

Create a control file based on the configuration file

# Generate control file in ./dist folder
$ node_modules/.bin/ngsw-config dist ./src/ngsw-config.json
ngsw-config outputFolder configurationFile baseHref

Configuration file

ngsw-config.json

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

Build-time resources

assetGroups

{
    "name": "appshell",
    "installMode": "prefetch",
    "updateMode": "lazy",
    "resources": {}
}

Build-time resources

assetGroups / resources

"resources": {
    "files": [
        "/assets/**/*"
    ],
    "versionedFiles": [
        "/**/*.html",
        "/**/*.js",
        "/**/*.css"
    ],
    "urls": [
        "https://fonts.googleapis.com/css?family=Material+Icons",
        "https://fonts.gstatic.com/s/materialicons/v29/2fcr.woff2"
    ]
}

Runtime resources

dataGroups

{
    "name": "api-freshness",
    "urls": [
      "/breakingnews"
    ],
    "cacheConfig": {
      "strategy": "freshness",
      "maxSize": 10,
      "maxAge": "12h",
      "timeout": "1m"
    }
}

Runtime resources

dataGroups

{
    "name": "api-performance",
    "urls": [
      "/archive"
    ],
    "cacheConfig": {
      "strategy": "performance",
      "maxSize": 100,
      "maxAge": "365d"
    }
}

Control file

ngsw.json

{
  "configVersion": 1,
  "index": "/index.html",
  "assetGroups": [...],
  "dataGroups": [...],
  "hashTable": {
    "/assets/favicon-16x16.png": "a153ae82ddbafe...",
    "/assets/favicon-32x32.png": "5e6c0c6faee615...",
    "/assets/images/logo.png": "90a19d7780c0...",
    ...
  }
}

Build flow

  1. Build production version of the app

  2. Generate  a control file for NGSW using NGSW CLI

  3. Copy ngsw-worker.js to the dist folder

“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”
“build-prod-ngsw”:

Register a service worker

  • Adding registration script to index.html

  • Using the same code in main.ts after bootstrapModule()

  • Using register() method of ServiceWorkerModule

ServiceWorkerModule

app.module.ts

import { ServiceWorkerModule } from '@angular/service-worker'
...

@NgModule({
  imports: [
    ...
    ServiceWorkerModule.register('/ngsw-worker.js')
  ]
  ...
})
export class AppModule { }

Demo

Pros

  • Essential features are codeless

  • Doing things in Angular way

Cons

  • Way too experimental at the moment

  • Intended to play a main service worker's role in your PWA

 

On the roadmap

  • Integration with Angular CLI

  • Integration with server-side rendering flow

  • Make Angular app progressive by default

# Generates ngsw-config.json with smart defaults
# And imports ServiceWorkerModule.register()
$ ng new --service-worker
# Builds an app, generates ngsw.json, copies ngsw-worker.js
$ ng build --prod
# Install the Workbox CLI
$ npm install workbox-cli --global
# Generate a service worker with some smart defaults
$ workbox generate:sw

App shell

Runtime caching

Offline GA

Replay failed requests

Broadcast updates

Build integrations

Workbox 101

module.exports = {
  "globDirectory": "dist/",
  "globPatterns": [
    "**/*.{txt,png,ico,html,js,json,css}"
  ],
  "swDest": "dist/sw-default.js",
  "globIgnores": [
    "3rdpartylicenses.txt"
  ]
};

workbox-cli-config.js

App shell

importScripts('workbox-sw.prod.v2.0.0.js');

const fileManifest = [
  {
    "url": "index.html",
    "revision": "ab950af06a80f755cd4bc1e34b3d6641"
  },
  ...
];

const workboxSW = new self.WorkboxSW();
workboxSW.precache(fileManifest);

sw-default.js

Generated service worker

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .then(() => {

    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw-default.js')
    }

  });

main.ts

Registration

Caching and serving

Our own service worker

"assets": [
  {
    "glob": "workbox-sw.dev.v2.0.0.js",
    "input": "../node_modules/workbox-sw/build/...",
    "output": "./"
  },
  ...
]

.angular-cli.json

$ npm install --save workbox-sw
$ npm install --save-dev workbox-build

Our own service worker

importScripts('./workbox-sw.dev.v2.0.0.js')

const workboxSW = new WorkboxSW()
workboxSW.precache([])

my-serviceworker.js

const apiStrategy = workboxSW.strategies.networkFirst()

workboxSW.router.registerRoute(
  /(http[s]?:\/\/)?([^\/\s]+\/)(api)/,
  apiStrategy
)

Injecting manifest

const swBuild = require('workbox-build')

swBuild
  .injectManifest({
    globDirectory: 'dist/',
    globPatterns: [
      '**/*.{txt,png,ico,html,js,json,css}'
    ],
    globIgnores: ['3rdpartylicenses.txt'],
    swSrc: './src/my-serviceworker.js',
    swDest: './dist/my-serviceworker.js'
  })

build-sw-workbox.js

Background sync

$ npm install --save workbox-routing
$ npm install --save workbox-runtime-caching
$ npm install --save workbox-background-sync
importScripts('./workbox-routing.dev.v2.0.0.js')
importScripts('./workbox-runtime-caching.dev.v2.0.0.js')
importScripts('./workbox-background-sync.dev.v2.0.0.js')
"assets": [
  ...
]

.angular-cli.json

my-serviceworker.js

We need to go deeper

backgroundSync.QueuePlugin

runtimeCaching.RequestWrapper

runtimeCaching.NetworkOnly

routing.RegExpRoute

routing.Router

workboxSW

Demo

Pros

  • Can extend existing service worker

  • Feature-rich

 

Cons

  • Extra build step needed

  • Could be more developer friendly

 

Flexibility

Automation

Stability

Thank you!

@webmaxru

Maxim Salnikov

Questions?

Creating Angular Progressive Web App: Which Option Will Work Better For You

By Maxim Salnikov

Creating Angular Progressive Web App: Which Option Will Work Better For You

It eventually happened: Progressive Web Applications took a worthy place in the modern web landscape, and there is no more need to convince developers why to go for performant, reliable, and engaging apps. Your Angular application is not the exception: adding PWA features is getting it to the next level of user experience. We have at least two very interesting options to get there. First, the native Angular Service Worker (NGSW) by Angular team, super-powered by Angular CLI and some extra ng-pwa-tools. Second, the all new framework-agnostic Workbox library by Google Chrome team. What's easier to set up for your Angular app? What has wider functionality? What's faster and more robust? Let's go exploring, coding and testing! You will have 100% full overview of these two approaches after my session, but the final decision is only yours!

  • 3,710