with Angular + Workbox
https://slides.com/agesteira/pwas-with-angular-and-workbox
Andrés Gesteira
Twitter: Andres Gesteira (@GeorgeKaplan_G)
Loves 70s and 80s films.
Senior Product Engineer
Angular Developer
Lives in Berlin

Progressive
Web
Applications
What makes a Web App "progressive"?
It loads even when we are offline => App Shell
It is installable => Web App Manifest
It is cross-platform => Also desktop!
App Shell
It is the minimal HTML, CSS and JS to power the user interface.
You can think of it as the substitute of the SDK in a mobile context.
This approach relies on aggressively precaching files.
It shows the First Contentful Paint
The magic technology to do that is called Service Workers.
User Centric Metrics
From a user's perception page loads also have lifecycles

Text
Screenshot from the Google I/O 2017 conference.
The App Manifest
A PWA is installable if the platform has support for this:
<link rel="manifest" href="/manifest.json">
{
"short_name": "Maps",
"name": "Google Maps",
"icons": [
{
"src": "/images/icons-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/images/icons-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/?launch=pwa",
"background_color": "#3367D6",
"display": "standalone",
"orientation": "landscape",
"scope": "/maps/",
"theme_color": "#3367D6"
}
Service Workers
JavaScript Threads
`window` in a browser.
`global` in Node.js
`self` in workers.
* If you just want to get the global object regardless of the context you need to use the `globalThis` property.
Workers
Web Workers: they offload heavy processing from the main thread.
*Worklets: they give access to low-level parts of the rendering pipeline.
Service Workers: event driven workers that act as a proxy servers.
* Houdini uses the `PaintWorklet` under the hood.
*Service Workers...
...are a replacement for the deprecated Application Cache.
...follow the the Extensible Web Manifesto philosophy.
...offer offline capabilities such as Push Notifications and Background Sync.
* Check out Jake Archibald's site about Service Workers.
...are request interceptors either to the Network or to the Cache Storage.
...only run over HTTPS or http://localhost, for security reasons.
Service Workers Lifecycle
Ensures that the page is controlled by only 1 version of the Service Worker.
It is made of 3 events:
- Download: the service worker is requested through a registration.
- Install: when the downloaded service worker file is found to be new.
- Activate: it allows the Service Worker to control clients (pages).
It can have 2 possible scenarios:
A. Newly created Service Worker.
B. Updated Service Worker.
Workbox
What is Workbox?
We register our Service Worker as normal...
...but in the worker thread we can start using the libraries:
It is a set of libraries that simplifies that process.
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js');
if (workbox) {
console.log(`Yay! Workbox is loaded 🎉`);
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
Precaching
Wraps all 3 service worker lifecycle events with very few lines of code:
workbox.precaching.precacheAndRoute([
'/styles/index.0c9a31.css',
'/scripts/main.0d5770.js',
{ url: '/index.html', revision: '383676' },
]);
Runtime Caching
This is what we natively did with the fetch event but now using Workbox:
workbox.routing.registerRoute(
'/logo.png',
handler
);
Common Runtime Caching Strategies
The Workbox Strategies package handles the most common scenarios:
- Cache Only: the Service Worker forces a response from the cache and never from the network.
- Network Only: the Service Worker forces a response from the network and never from the cache.
- Cache First falling back to network: the Service Worker tries the cache first and if there is no cached response it goes to the network. But most importantly: the response from the network is cached before being passed to the browser.
- Network First falling back to cache: the Service Worker tries the network first. If the request is successful the response is cached before being passed to the browser. If the request fails it falls back to the last cached response.
- Stale While Revalidate: here we only use responses from the cache but we also make a call to the network and if that call is successful we cache that response for the next time.
Check out the Rick And Morty PWA
A framework-agnostic workshop:
NGSW
Add a Service Worker
On an existing Angular project.
ng add @angular/pwa --project *project-name*
* Project flag is only necessary for non-default apps.
*

npm i lite-server -D
ng build --prod
npx lite-server --baseDir="dist/app-name"
Changes: package.json

@angular/pwa is an schematics package.
@angular/service-worker: ServiceWorkerModule, SwUpdate & SwPush.
lite-server is good for serving SPAs.
Changes: manifest.webmanifest (I)

The extension is irrelevant.
*.webmanifest is more convenient for some server configs.
Changes: manifest.webmanifest (II)


Changes: app.module.ts

Changes: ngsw-config.json (I)

Changes: ngsw-config.json (II)

dataGroups
export interface DataGroup {
name: string;
urls: string[];
version?: number;
cacheConfig: {
maxSize: number;
maxAge: string;
timeout?: string;
strategy?: 'freshness' | 'performance';
};
}
freshness === Network First falling back to cache
performance === Cache First falling back to network
Build

Serve

Angular + Workbox (demo)
Initial settings
On an existing Angular project.
npm i lite-server lighthouse workbox-cli -D
"scripts": {
"build": "ng build --prod",
"lighthouse": "npx lighthouse http://localhost:3000/ --view --chrome-flags=\"--headless\"
--output-path=./lighthouse/lighthouse_\"$(date \"+%Y-%m-%d_%H-%M-%S\")\".report.html",
"lint": "ng lint",
"ng": "ng",
"serve:dev": "ng serve",
"serve:prod": "npx lite-server --baseDir=\"dist/app-name\"",
"start": "npm run serve:dev"
},
# .gitignore
/lighthouse/**.report.html
npm run build
npm run serve:prod
npm run lighthouse
Go offline => Dino Game

manifest.json
Generate Manifest with https://app-manifest.firebaseapp.com/
Copy icons folder to assets folder.
Copy generated file to src/manifest.json
Modify src/manifest.json.
Add src/manifest as an asset in angular.json.
index.html
Add meta tags:
Add link tags:
Add noscript tag:
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="Manifest name property" />
<meta name="description" content="Description" />
<meta name="theme-color" content="#xxx" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/assets/img/icons/angular-logo-512x512.png" />
<noscript>Please enable JavaScript to continue using this application.</noscript>
npm run build
npm run lighthouse
Add Service Worker (I)
Extend build script in package.json:
Create src/sw-custom.js:
"... && npx workbox copyLibraries dist/app-name && npx workbox injectManifest",
importScripts('/workbox-vx.x.x/workbox-sw.js');
if (workbox) {
console.log(`Yay! Workbox is loaded 🎉`);
workbox.setConfig({
modulePathPrefix: '/workbox-vx.x.x/'
});
workbox.precaching.precacheAndRoute([]);
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL('/index.html'));
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
Add Service Worker (II)
Create workbox-config.js:
module.exports = {
globDirectory: 'dist/app-name',
globPatterns: ['**/*.{txt,ttf,otf,css,png,ico,html,js,json,mjs}'],
swDest: 'dist/app-name/sw.js',
swSrc: 'src/sw-custom.js',
globIgnores: ['workbox-vx.x.x/**/*']
};
Add Service Worker (III)
Register Service Worker in main.ts
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(() => {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then(
registration => {
console.log(`Service Worker registered! Scope: ${registration.scope}`);
},
error => {
console.error(`Service Worker registration failed: ${error}`);
}
);
});
}
})
.catch(err => console.error(err));
npm run build
npm run lighthouse
Go offline => We keep our app

Install and uninstall

Warning
This implementation is going to change with Workbox 5.

NGSW vs Workbox
NGSW
Easy to start.
Seamless integration with Angular (SwUpdate, SwPush).
Includes Safety Worker.
Only allows 2 runtime caching strategies.
We cannot access the service worker.
Workbox
Full power of your own Service Worker.
Maximum flexibility with configurations.
Rich functionality for professional PWA development.
It requires a better understanding of Service Workers.
We lose @angular/service-worker features so we need to write them.
Wanna know more?
A series of articles coming soon
Workbox 5 implementation.
How to replace SwUpdate & SwPush.
How to set caching routes and debug.

And much more.
Thank you!
Andres Gesteira @GeorgeKaplan_G
PWAs with Angular and Workbox
By Andres Gesteira
PWAs with Angular and Workbox
- 1,097