All Projects → mikegeyser → building-pwas-with-angular

mikegeyser / building-pwas-with-angular

Licence: other
Building Progressive Web Apps with Angular - Devconf 2018

Programming Languages

HTML
75241 projects
javascript
184084 projects - #8 most used programming language
CSS
56736 projects
typescript
32286 projects

Projects that are alternatives of or similar to building-pwas-with-angular

hyperapp-todo-parcel
marcusasplund.github.io/hyperapp-todo-parcel/
Stars: ✭ 12 (-7.69%)
Mutual labels:  pwa, workbox
jekyll-pwa
Jekyll plugin for PWA
Stars: ✭ 95 (+630.77%)
Mutual labels:  pwa, workbox
Elm Hn Pwa
Hacker News as a PWA built with Elm
Stars: ✭ 43 (+230.77%)
Mutual labels:  pwa, workbox
pwa-cookbook
A step-by-step guide to Progressive Web Applications
Stars: ✭ 21 (+61.54%)
Mutual labels:  pwa, progressive-web-apps
Pwatter
Angular Progressive Web App using Workbox
Stars: ✭ 167 (+1184.62%)
Mutual labels:  pwa, workbox
React Pwa Webpack Starter
Boilerplate to build a React PWA with Webpack + Workbox
Stars: ✭ 38 (+192.31%)
Mutual labels:  pwa, workbox
Starter React Flux
Generate your React PWA project with TypeScript or JavaScript
Stars: ✭ 65 (+400%)
Mutual labels:  pwa, workbox
Next Pwa
Zero config PWA plugin for Next.js, with workbox 🧰
Stars: ✭ 909 (+6892.31%)
Mutual labels:  pwa, workbox
Pwafire
Progressive Web Apps API of APIs
Stars: ✭ 137 (+953.85%)
Mutual labels:  pwa, workbox
Pwa Asset Generator
Automates PWA asset generation and image declaration. Automatically generates icon and splash screen images, favicons and mstile images. Updates manifest.json and index.html files with the generated images according to Web App Manifest specs and Apple Human Interface guidelines.
Stars: ✭ 1,787 (+13646.15%)
Mutual labels:  pwa, progressive-web-apps
Pwa Module
Zero config PWA solution for Nuxt.js
Stars: ✭ 1,033 (+7846.15%)
Mutual labels:  pwa, workbox
workbox-in-js-framework
🍳 Code sample for using Workbox in various JS framework
Stars: ✭ 16 (+23.08%)
Mutual labels:  pwa, workbox
Next Offline
make your Next.js application work offline using service workers via Google's workbox
Stars: ✭ 1,306 (+9946.15%)
Mutual labels:  pwa, workbox
Vite Plugin Pwa
Zero-config PWA for Vite
Stars: ✭ 209 (+1507.69%)
Mutual labels:  pwa, workbox
jekyll-pwa-workbox
A Jekyll plugin using Workbox to make your PWA / Website available offline.
Stars: ✭ 22 (+69.23%)
Mutual labels:  pwa, workbox
bangle-io
A web only WYSIWYG note taking app that saves notes locally in markdown format.
Stars: ✭ 626 (+4715.38%)
Mutual labels:  pwa
react-prpl-boilerplate
A simple boilerplate for learn PRPL pattern with React.
Stars: ✭ 23 (+76.92%)
Mutual labels:  pwa
Blog
📔 个人博客,前端小册子
Stars: ✭ 35 (+169.23%)
Mutual labels:  pwa
donations
Grounding corporate donations in the reality of the individual
Stars: ✭ 17 (+30.77%)
Mutual labels:  pwa
hackaru-web
An web server for Hackaru
Stars: ✭ 20 (+53.85%)
Mutual labels:  pwa

Building Progressive Web Apps with Angular

This talk was presented at DevConf South Africa 2018, in both Johannesburg and Cape Town. The slides can be viewed here.

The holy grail of web applications is having them be near-native performance, and to a large degree this has driven the JavaScript revolution. To truly realise that goal we need to embrace native design thinking for our applications, applying it not only to our code but also to our content. Using ideas from Progressive Web Applications and a modern framework such as Angular we can create engaging, fast and available application experiences - the best of both native and web.

This code-oriented talk will show how to practically use select principles of Progressive Web Applications in conjunction with Angular to create fast and compelling web experiences. It will cover the creation of an application shell and go into using service workers for preloading, caching and even offline support.

The Bio: Mike Geyser is a programmer at BBD in the R&D team, co-organiser of the Jozi.JS meetup group, a frequent technical speaker, and loves the web. He has been hacking on it since Geocities was a thing, and has the keening wail of dialup modem etched into his subconscious. While he is fluent in several ‘golden hammer’ programming languages, he has a long-lived love affair with JavaScript, and cannot see its (many, obvious) flaws. He has worked on lots of interesting enterprise applications, but it is the challenges of the public web that really appeal to him - having spent his formative years building transactional websites. He is always eager to talk about the web platform, but be warned, he is prone to hyperbole.

The Twitters: @mikegeyser

The Demo!

The things I will be doing to convert an angular app to a PWA are:

  1. Create an app manifest
  2. Create an app shell
  3. Precache all essential assets
  4. Cache any dynamic data returned from the api, including images.
  5. Queue failed updates using IndexDb

1. App Manifest

Create a manifest.json file.

Snippet: _manifest1

src/manifest.json

{
  "name": "Meme Wrangler 9000",
  "short_name": "MemeWrangler",
  "theme_color": "#3f51b5",
  "background_color": "#ffffff",
  "display": "standalone",
  "Scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "assets/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "splash_pages": null
}

Add the manifest to the .angular-cli.json so that it will be included in the build output.

Snippet: _manifest2

.angular-cli.json

"assets": [
    "assets",
    "favicon.ico",
    "manifest.json"
],

ng serve

Snippet: _manifest3

src/index.html

<link rel="manifest" href="/manifest.json">

And then you can see the manifest loaded up in Chrome.

Open chrome and show manifest

2. App Shell

src/index.html

<app-root>This will only display while the app is loading!</app-root>

Open the index.html in Chrome as a file, to show it without the application mounted.

Change the inner html from a simple string to some markup.

Snippet: _shell1

    <div class="shell">
      <div class="title">loading...</div>
      <div class="meme">&nbsp;</div>
      <div class="meme">&nbsp;</div>
      <div class="meme">&nbsp;</div>
    </div>

Add some styling to sketch out what the page is going to look like.

Snippet: _shell2

  <style>
    html,
    body {
      margin: 0;
    }

    .shell .title {
      background-color: #3f51b5;
      color: #fff;
      text-align: center;
      font: 32px sans-serif;
      line-height: 56px;
    }

    .shell .meme {
      margin: 0.5em 1em;
      background-color: #e0e0e0;
      min-height: 250px;
    }
  </style>

Rebuild the solution.

3.1. Precache all essential assets

>> npm install -g workbox-cli

We can generate a service worker using workbox

>> workbox wizard
? What is the root of your web app (i.e. which directory do you deploy)? dist/
? Which file types would you like to precache? txt, png, ico, html, js, json, css
? Where would you like your service worker file to be saved? dist/sw.js
? Where would you like to save these configuration options? workbox-config.js

Generate the service worker.

>> workbox generateSW workbox-config.js

Open the src/workbox-config.js and dist/sw.js and show the generated code.

A more powerful way to do this is not via config alone, but a template sw.js.

Snippet: _sw1

src/sw.js

importScripts("workbox-v3.0.1/workbox-sw.js");

const precacheManifest = [];

workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(precacheManifest);

Change the config to respect the inject manifest.

Snippet: _config1

workbox-config.js

  "swSrc": "src/sw.js",
  "injectionPointRegexp": /(const precacheManifest = )\[\](;)/

Install the service worker in the src/main.ts file.

src/main.ts

Register the service worker after the angular app has booted. Check to make sure that this is a production build, and that service worker is actually available. Then, register it.

Snippet: _install1

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .then(registerServiceWorker);

function registerServiceWorker() {
  if (environment.production && 'serviceWorker' in navigator) {
    navigator.serviceWorker.register('sw.js');
  }
}

Then we can build and test it.

>> ng build --prod

new tab

>> workbox injectManifest workbox-config.js

>> workbox copyLibraries dist/

>> http-server dist/ -c 0

We're going to do this a lot, so best to add it to the package.json scripts.

Snippet: _config2

package.json

"start-sw": "workbox injectManifest workbox-config.js && workbox copyLibraries dist/ &&  http-server dist -c 0"
>> yarn start-sw

Open Chrome show sw.js registration, and the network panel.

Take a look at the Application tab in Chrome dev tools, to see the service worker installed. Switching the network offline will show that the assets are served from cache.

4.1. Cache api data

Add a caching route for categories.

Snippet: _sw2

src/sw.js

const dataCacheConfig = {
    cacheName: 'meme-data'
};

workbox.routing.registerRoute(/.*categories/, workbox.strategies.cacheFirst(dataCacheConfig), 'GET');

Open Chrome and verify that the categories are being served from service worker. Check that the categories are being stored in the cache. Show the offline behaviour.

Cache the other routes of interest.

Snippet: _sw3

src/sw.js

workbox.routing.registerRoute(/.*templates/, workbox.strategies.cacheFirst(dataCacheConfig), 'GET');
workbox.routing.registerRoute(/.*memes\/.\w+/, workbox.strategies.staleWhileRevalidate(dataCacheConfig), 'GET');
    

4.2. Cache images

Add some simple image caching (before the data caching).

Snippet: _sw4

src/sw.js

workbox.routing.registerRoute(
    /.*.(?:png|jpg|jpeg|svg)$/,
    workbox.strategies.cacheFirst({
        cacheName: 'meme-images'
    }), 
    'GET');

Open Chrome, and update the sw.js using skipWaiting. Show network panel, show cache storage. Take app offline.

Open up in Chrome, show the images stored in the cache, and then the storage summary. Take the application offline, and show it working.

5. Proper updating for the service worker.

We can automate the skip loading that we've been doing manually to force the install of the service worker.

Snippet: _sw5

src/sw.js

self.addEventListener('install', function(event) {
  self.skipWaiting();
});

This will suffice for our current need, but when the service worker updates the page will already have loaded. This means that we will need to reload the page to make full use of the updated service worker.

6.1 Offline updates

Workbox has a built in queue mechanism, based off of IndexDB and the background sync api.

Snippet: _sw6

src/sw.js

const queue = new workbox.backgroundSync.Queue('memes-to-be-saved');

Can listen to all fetch events, that is essentially any http request proxied through service worker. On the event we can opt to filter by url and method, to only deal with the POST to the memes api.

Snippet: _sw7

src/sw.js

self.addEventListener('fetch', (event) => {
  if (event.request.url.match(/.*memes/) && event.request.method === 'POST') {

  }
});

We can then elect to make the call on behalf of the request. We expect that request to fail if the app is offline, so we catch the error and add it to the background sync queue.

Snippet: _sw8

src/sw.js

let response = fetch(event.request.clone());
                .catch((err) => {
                    return queue.addRequest(event.request.clone())
                });

event.respondWith(response);

Wish it were this simple. Cache invalidation joke.

6.2 Offline updates (take 2)

This would work, but means that the rest of the application is ignorant to the updates. We can do better, so that the offline support is transparent.

Snippet: _sw9

src/sw.js

  /*
      1. Submit request
      2. Invalidate cache (if successful)
      3. Queue the change (if failed)
  */
  
  let response = fetch(event.request.clone())
      .then(actualResponse => invalidateCache(event.request.clone(), actualResponse))
      .catch(_ => queueChange(event.request.clone()));

Everything is a promise.

Snippet: _sw10

src/sw.js

function invalidateCache(request, actualResponse) {
    /*
        1. Read the request data.
        2. Open the cache.
        3. Delete anything that matches the url.
        4. Return the actual response.
     */

    return request.json()
        .then(requestData => {
            const url = `${request.url}/${requestData.category}`;
            
            return caches.open('meme-data')
                .then(cache => cache.delete(url));
        })
        .then(_ => actualResponse);
}

Snippet: _sw11

src/sw.js

function queueChange(request) {
    /*
        1. Queue the change.
        2. Read the request data.
        3. Open the cache.
        4. Find the matching response.
        5. Read the cached response.
        6. Create a new response.
        7. Update the cached response.
        8. Return a fake response.
     */

    return queue.addRequest(request.clone())
        .then(_ => request.json())
        .then(requestData => {
            requestData['offline'] = true;
            const url = `${request.url}/${requestData.category}`;

            return caches.open('meme-data')
                .then(cache => {
                    return cache.match(url)
                        .then(cachedResponse => cachedResponse.json())
                        .then(data => {
                            const updatedRequest = [requestData, ...data];

                            const fakeResponse = new Response(
                                JSON.stringify(updatedRequest),
                                { status: 200 });

                            return cache.put(url, fakeResponse.clone())
                                .then(_ => fakeResponse.clone());
                        });
                });
        });
}

Force background sync using the Chrome Devtools.

  > workbox-background-sync:memes-to-be-saved
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].