using Angular Service Worker

Maxim Salnikov

Angular GDE

Automatic

Progressive Web Apps 

How to create an Angular Progressive Web App?

Natively

Maxim Salnikov

  • Full-stack engineer at ForgeRock

  • Google Developer Expert in Angular

  • PWA evangelist / trainer

  • ngVikings conference organizer

ngVikings.org

  • All sides of Angular ecosystem presented by international experts

  • 100% community-driven event from developers for developers

  • Only technical and practical Angular content

March 1-2, Helsinki, Finland

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

Logically

Physically

JS

-file

App

Service worker

Angular Service Worker

Wait a sec!

  • 1000+ developers

  • Major browsers/frameworks/libs reps

Generate a new Angular PWA

$ ng new myProgressiveApp --service-worker

Starting from Angular CLI 1.6

$ ng build --prod

ngsw-worker.js

ngsw.json

dist/

Adding NGSW to the existing app

$ ng set apps.0.serviceWorker=true
$ npm install @angular/service-worker --save

1. Install the package

2. Enable build support

3. Register NGSW for your app

4. Create configuration file

Service worker build support in the CLI

Build

Copy

src/ngsw-config.json

dist/ngsw.json

ngsw-worker.js

dist/

node_modules/@angular...

Can be npm-scripted for Angular CLI 1.5!

Registering NGSW

import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
...
@NgModule({
  ...
  imports: [
    ...


  ]
})
export class AppModule { }
    ServiceWorkerModule.register('/ngsw-worker.js',
        { enabled: environment.production }),

app.module.ts

NGSW configuration file

src/ngsw-config.json

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

App shell

assetGroups

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

App shell resources

assetGroups / "app" / resources

"resources": {









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

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/**"
    ]

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"
    }

Push notifications

import { SwPush } from '@angular/service-worker';
constructor(private swPush: SwPush) {}
subscribeToPush() {
  this.swPush.requestSubscription({
    serverPublicKey: this.VAPID_PUBLIC_KEY
  })
    .then(pushSubscription => {
      // Pass subscription object to 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"
      }
    ],
    ...

App version updates

v1

v2

v1

v1

v2

Server

Browser

v2

Check for updates

import { SwUpdate } from '@angular/service-worker';
constructor(private swUpdate: SwUpdate) {}
this.swUpdate.available.subscribe(event => {
  let snackBarRef = this.snackBar
    .open('Newer version of the app is available', 'Refresh');

  snackBarRef.onAction().subscribe(() => {
    window.location.reload()
  })
})

updates.component.ts

Hint #1: Debugging update flow

1. Keep DevTools closed & wait...

3. Use code snippets 

Every time the Angular service worker starts, it checks for updates to the app by looking for updates to the ngsw.json manifest.

this.swUpdate.available.subscribe(event => {});

Behaviour to be changed

2. Unload browser from memory

this.swUpdate.checkForUpdate()

- OR -

Hint #2: Checking the status

https://yourwebsite.com/ngsw/state

NGSW Debug Info:

Driver state: NORMAL ((nominal))
Latest manifest hash: cd4716ff2d3e24f4292010c929ff429d9eeead73
Last update check: 9s215u

=== Version 34c3fd2361735b1330a23c32880640febd059305 ===

Clients: 7eb10c76-d9ed-493a-be12-93f305394a77

=== Version cd4716ff2d3e24f4292010c929ff429d9eeead73 ===

Clients: ee22d69e-37f1-439d-acd3-4f1f366ec8e1

=== Idle Task Queue ===
Last update tick: 4s602u
Last update run: 9s222u
Task queue:


Debug log:

Hint #3: Kill switch

1. Long way

ng set apps.0.serviceWorker=false
ng build --prod
...deploy

2. Short way

rm dist/ngsw.json
...deploy

Main available features

App Shell

Runtime Caching

Push Notifications

Smart Updates

Angular Service Worker advantages

  • Essential features are config-driven

  • Decoupled updates model

  • Integrity checks

  • Doing things in Angular way

On the roadmap

  • Integration with server-side rendering (app-shell)

  • Better debugging support

  • Make Angular app progressive by default

  • Documentation!

Thank you!

@webmaxru

Maxim Salnikov

Questions?