[DRAFT]

Service Workers: understand and use

Stepan Suvorov

#standWithUkraine 🇺🇦

Agenda

?

use

Angular

Service Workers?

ServiceWorkers

vs

WebWorkers

ServiceWorkers

vs

Angular Interceptors

Angular Interceptors

@Injectable()
export class RetryInterceptor implements HttpInterceptor {
  intercept(httpRequest: HttpRequest<any>, 
            next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(httpRequest).pipe(retry(2));
  }
}
  • Authentication
  • Caching
  • Profiling
  • Error handling
  • Mock backend
  • Loaders & Notifications
  • Headers management
  • ....

Angular Interceptors

Interceptors vs ServiceWorkers

Angular

HttpClient

Interceptors

ServiceWorkers

Interceptors vs ServiceWorkers

HttpClient

All requests

  • the page itself (index.html)
  • assets (js, images, css)
  • AJAX (XMLHttpRequest, fetch)

What are Service Workers?

  • can't access the DOM directly
  • control how network requests from your page
  • async → extensive use of promises
  • HTTPS required
  • Reliability
  • Performance
  • Push Notifications

Use

navigator.serviceWorker.register("/sw.js");
navigator.serviceWorker.register"/sw.js", 
                                { scope: "/"});

Registering ServiceWorker

Why is my service worker failing to register?

This could be for the following reasons:

  1. You are not running your application through HTTPS.
  2. The path to your service worker file is not written correctly — it needs to be written relative to the origin, not your app's root directory. In our example, the worker is at https://bncb2v.csb.app/sw.js, and the app's root is https://bncb2v.csb.app/. But the path needs to be written as /sw.js.
  3. It is also not allowed to point to a service worker of a different origin than that of your app.

sw.js

ServiceWorker Events

install
self.addEventListener("install", (event) => {

  //...

});
self.addEventListener("install", (event) => {

event.waitUntil(addToCache(["/", "/index.html", "/some.js"...]));

});
const addToCache = async (resources) => {
  const cache = await caches.open("v1");
  await cache.addAll(resources);
};
activate

activate vs install

fetch
self.addEventListener("fetch", (event) => {
  event.respondWith(caches.match(event.request));
});
message
 navigator.serviceWorker.ready.then((registration) => {
    registration.active.postMessage("Hi service worker");
  });
addEventListener("message", (event) => {
  // event is an ExtendableMessageEvent object
  console.log(`The client sent me a message: ${event.data}`);

  event.source.postMessage("Hi client");
});
navigator.serviceWorker.addEventListener('message', (event) => {
  console.log(event.data);
});
sync
self.addEventListener('periodicsync', (event) => {
  if (event.tag === 'fetch-news') {
    event.waitUntil(fetchAndCacheLatestNews());
  }
});

  const registration = await navigator.serviceWorker.ready;
  await registration.periodicSync.register('fetch-news', {
      minInterval: 24 * 60 * 60 * 1000,
  });

push
self.addEventListener("push", (event) => {
  const message = event.data.json();
  
  
});

Security Time

Do you know how many SWs already registered in your browser?

Service Workers

in Angular

Install with AngularCli

ng add @angular/pwa

  • @angular/service-worker package
  • index.html
  • build config →  sw bundle to dist
  • SW in the app module
  • ngsw-manifest.json
  • offline mode (cache)

Cache our of box

What's being cached?

  • index.html
  • favicon.ico
  • build artifacts (js and css bundles)
  • assets

ngsw-config.json

{
  "$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/manifest.webmanifest",
          "/*.css",
          "/*.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

ngsw-config

  • installMode
  • updateMode
  • prefetch
  • lazy

Caching External Resources

{
  "$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    ...
    {
    "name": "externals",
    "installMode": "lazy",
    "updateMode": "lazy",
    "resources": {
        "urls": ["https://angular.io/assets/images/logos/angular/angular.svg"]
    }
    }
    ...
  ]
}

Caching HTTP Requests

{
  ...
  "assetGroups": [ ... ],
  "dataGroups": [
    {
      ...
    },
    {
      ...
    }
  ]
}

dataGroups

export interface DataGroup {
  name: string;
  urls: string[];
  version?: number;
  cacheConfig: {
    maxSize: number;
    maxAge: string;
    timeout?: string;
    strategy?: 'freshness' | 'performance';
  };
  cacheQueryOptions?: {
    ignoreSearch?: boolean;
  };
}

dataGroups

strategy: 'freshness' | 'performance';
  • performance(default) - cache first

  • freshness - network first

dataGroups

"dataGroups": [
    {
        "name": "random.org",
        "urls": ["https://api.random.org/**"],
        "cacheConfig": {
            "maxSize": 20,
            "maxAge": "7d",
            "strategy": "freshness"
        }
    }
]

Updates

  constructor(swUpdate: SwUpdate) {

    swUpdate.available.subscribe(event => {
      console.log('UPDATE!');

      // user prompt?
      // document.location.reload());

    });
  }

Thank you for your attention.

Questions?

Feedback time

Service Workers: understand and use

By Stepan Suvorov

Service Workers: understand and use

  • 470