Progressive

Web Apps

using the Angular Mobile Toolkit

Maxim Salnikov

  • Google Developer Expert in Angular

  • AngularJS Oslo Meetup organizer

  • ngVikings conf organizer

Products from the future

UI Engineer at ForgeRock

Google Experts are a global network of experienced product strategists, designers, developers and marketing professionals actively supporting developers, startups and companies changing the world through web and mobile applications.

Google Developer

Experts

Websites timeline

Static

 

Dynamic

 

AJAX

 

RWD

 

PWA

Progressive Web App

Progressive Web App (PWA) is a term used to denote a new software development methodology. Unlike traditional applications, Progressive Web App can be seen as an evolving hybrid of regular web pages (or websites) and a mobile application. This new application life-cycle model combines features offered by most modern browsers with benefits of mobile experience.

PWA?

PWA?

  • Progressive
  • Discoverable
  • Linkable
  • App-like
  • Responsive
  • Connectivity-independent
  • Re-engageable
  • Installable
  • Fresh
  • Safe

Progressive

By definition, a progressive web app must work on any device and enhance progressively, taking advantage of any features available on the user’s device and browser.

Discoverable and Linkable

Because a progressive web app is a website, it should be discoverable in search engines. This is a major advantage over native applications, which still lag behind websites in searchability.

Re-engageable

Mobile app users are more likely to reuse their apps, and progressive web apps are intended to achieve the same goals through features such as push notifications.

Installable

A progressive web app can be installed on the device’s home screen, making it readily available.

Connectivity-independent

It should work in areas of low connectivity or offline.

The app must be capable of starting offline and still display useful information.

Service worker

  • Make the website function offline

  • Increase online performance by reducing network requests for certain assets

  • Provide a customized offline fallback experience

of PWA

Service worker

  • Background data synchronization

  • Responding to resource requests from other origins

  • Receiving centralized updates to expensive-to-calculate data such as geolocation or gyroscope, so multiple pages can make use of one set of data

  • Client-side compiling and dependency management of CoffeeScript, less, CJS/AMD modules, etc. for dev purposes

  • Hooks for background services

  • Custom templating based on certain URL patterns

  • Performance enhancements, for example pre-fetching resources that the user is likely to need in the near future, such as the next few pictures in a photo album.

Service worker

Service workers essentially act as proxy servers that sit between web applications, and the browser and network (when available).

  • A service worker is an event-driven worker registered against an origin and a path

  • A service worker is run in a worker context

  • Service workers only run over HTTPS, for security reasons

Service worker

Register

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(function(registration) {
    // Registration was successful
    console.log('Registration successful with scope: ', registration.scope);
  }).catch(function(err) {
    // registration failed :(
    console.log('Registration failed: ', err);
  });
}

Scope!

Install

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(URLS_TO_CACHE);
      })
  );
});

Files to cache!

Update

  1. Update your SW file. 

  2. Your new service worker will be started and the install event will be fired.

  3. At this point the old service worker is still controlling the current pages so the new service worker will enter a waiting state.

  4. When the currently open pages (tabs) of your site are closed, the old service worker will be killed and the new service worker will take control.

  5. Once your new service worker takes control, its activate event will be fired.

Update

self.addEventListener('activate', function(event) {

  var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];

  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );

});

Lifecycle

Intercepting requests

Involved APIs

  • Promises

  • Cache API

  • Fetch API

  • Notifications API

  • Push API

CanIUse?

Mobile Toolkit

Mobile Toolkit

ng new myApp --mobile

The --mobile flag has been disabled temporarily while we await an update
of angular-universal for supporting NgModule. Sorry for the inconvenience.

Mobile Toolkit

Mobile Toolkit

App Shell

App Shell

<html>
  <body>
    <app-root-component>
      <h1>Loading...</h1>
    </app-root-component>
  </body>
</html>
<html>
 <body>
    <app-root-component>
      <style>...</style>
      <div class="md-header">
        <h3>ngBudapest</h3>
      </div>
      <div class="md-progress-bar"></div>
    </app-root-component>
 </body>
</html>

App Shell

<html>
 <body>
    <app-root-component>
        <md-toolbar color="primary">

        <span>{{title}}</span>

        </md-toolbar>

        <md-progress-bar mode="indeterminate"></md-progress-bar>
    </app-root-component>
 </body>
</html>

App Shell

<html>
  <style>md-toolbar { display: flex; box-sizing: border-box;...</style>

  <body>
      <app-root-component _nghost-cb4a-1="">

      <md-toolbar _ngcontent-cb4a-1="" color="primary" class="md-primary" ng-reflect-color="primary">
        <div class="md-toolbar-layout">
          <md-toolbar-row> 
            <span _ngcontent-cb4a-1="">ngBudapest</span>
          </md-toolbar-row>
        </div>
      </md-toolbar>

      <md-progress-bar _ngcontent-cb4a-1="" aria-valuemax="100" aria-valuemin="0" mode="indeterminate" role="progressbar" _nghost-cb4a-3="" ng-reflect-mode="indeterminate" aria-valuenow="0"> <div _ngcontent-cb4a-3="" class="md-progress-bar-background"></div> <div _ngcontent-cb4a-3="" class="md-progress-bar-buffer"></div> <div _ngcontent-cb4a-3="" class="md-progress-bar-primary md-progress-bar-fill" ng-reflect-ng-style="[object Object]" style="transform:scaleX(0);"></div> <div _ngcontent-cb4a-3="" class="md-progress-bar-secondary md-progress-bar-fill"></div> </md-progress-bar>

    </app-root-component>
  </body>
</html>

App Shell

import { UniversalModule } from 'angular2-universal';

@NgModule({
  bootstrap: [AppComponent],
  imports: [
    AppModule,
    UniversalModule.withConfig({
      ...
    }),
})
export class AppShellModule {}

platformUniversalDynamic()
  .serializeModule(ShellModule)
  .then(html => console.log(html));

App Shell

# Install app-shell utility from github.com/angular/mobile-toolkit
$ npm install --save @angular/app-shell
// App code
import {AppShellModule} from '@angular/app-shell';

// In NgModule used for Universal prerendering:
AppShellModule.prerender()

// At runtime:
AppShellModule.runtime()

App Shell

@Component({
  selector: 'app-root-component',
  template: `
    <!-- Only show loading indicator in the shell -->
    <loading-indicator *shellRender>
    </loading-indicator>

    <!-- Hide a dynamic view until runtime -->
    <dynamic-view *shellNoRender>
    </dynamic-view>
    `
})
export class AppRootComponent {}

Service Worker

Service Worker

@angular/service-worker/bundles

@angular/service-worker/build

@angular/service-worker/companion

@angular/service-worker/worker

Pre-fetching and caching

# First, install the Angular Service Worker
$ npm install --save @angular/service-worker
<script type="text/javascript">
  // Require worker-basic.min.js copied to deploy directory from
  // node_modules/@angular/service-worker/bundles 

  // Feature detection guards against older browsers that don't
  // support service workers.
  if (navigator.serviceWorker) {
    navigator.serviceWorker.register('/worker-basic.min.js');
  }
</script>

Pre-fetching and caching

ngsw-manifest.json:

{
  "static": {
    "urls": {
      "/index.html": "ae543...",
      "/app.js": "9ff18...",
      "/logo.png": "0e33a...",
      ...
    }
  }
}
import AngularSWPlugin from '@angular/service-worker/webpack';

webpack({
  entry: 'index.html',
  output: {...},
  plugins: [
    ...,
    new AngularSWPlugin()
  ]
});

Dynamic Caching

{
  ...,
  "dynamic": {
    "match": [{
      "url": "/api",
      "prefix": true,
      "strategy": "fastest"
      "invalidate": [...]
    }]
  }
}

Coming soon

Companion

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

@NgModule({
    declarations: [
        DashboardComponent
    ],
    exports: [
        DashboardComponent
    ],
    imports: [
        ...
        ServiceWorkerModule
    ],
    providers: []
})

Companion

import {NgServiceWorker} from '@angular/service-worker';

constructor(public sw: NgServiceWorker) {
    sw.log().subscribe(message => this.log.push(message));
}
this
    .sw
    .checkForUpdate()
    .subscribe(res => {
        this.result = JSON.stringify(res);
    });
this
    .sw
    .ping()
    .subscribe(res => {
        this.result = JSON.stringify(res);
    });

Worker

import {bootstrapServiceWorker} from '../bootstrap';
import {StaticContentCache} from '../../plugins/static';
import {RouteRedirection} from '../../plugins/routes';
import {Push} from '../../plugins/push';

bootstrapServiceWorker({
  manifestUrl: '/ngsw-manifest.json',
  plugins: [
    StaticContentCache(),
    RouteRedirection(),
    Push(),
  ],
});

Push notifications

Backend

Push Service

Service worker

Push notifications

ngsw-manifest.json:

{
  ...,
  "push": {
    "showNotifications": true
  }
}
manifest.webapp

{
  ...,
  "gcm_sender_id": "12345"
} 

Push notifications

# web-push package nicely handles details of pushing data
$ npm install --save web-push
const webPush = require('web-push');
webPush.setGCMAPIKey(...);

let payload = {
  notification: {
    title: 'Hello from the server', body: '...', icon: '/icon.png'
  }
};
webPush.sendNotification({
  payload: new Buffer(JSON.stringify(payload)), …
});

Web Push Protocol

Push notifications

const webPush = require('web-push');
webPush.setGCMAPIKey(...);
webPush.setVapidDetails(
  'mailto:salnikov@gmail.com',
  'BHe82datF...',
  's-zBxZ1Kl...'
);

Push notifications

import {NgServiceWorker} from '@angular/service-worker';


// Inject an API for communicating with the Angular service worker
export class PushService {
  constructor(worker: NgServiceWorker) {

    worker.registerForPush().subscribe(endpoint => {
      // Send endpoint data to the server to complete registration
    };

    worker.push.subscribe(payload => {
      // Process payload
    });

  }
}

Mobile Toolkit

Hold On!

Köszönöm!

@webmaxru

Maxim Salnikov

Progressive Web Apps using the Angular Mobile Toolkit

By Maxim Salnikov

Progressive Web Apps using the Angular Mobile Toolkit

The term Progressive Web App refers to a group of technologies, such as service workers, and push notifications, that can bring native-like performance and user experience to web apps. Progressive Web Apps are interesting because in some ways they represent a coming of age for the Web. The Angular Mobile Toolkit makes it easy to build snappy Web apps that load instantly on any device, even without an internet connection. Take advantage of the searchability, shareability, and no-install-required-ability of the Web without compromise. During this 100% hands-on session we'll have a look on recent tools and guides from Angular team to help us build Progressive Web Apps. We'll have a look at Angular CLI and plugins/libraries for App Shell, Service Worker, and Application Manifest supporting us in fast and easy creation of installable, offline-capable, mobile-network-friendly apps. Agenda Introduction to PWA and Workshop Setup Introduction to Angular Mobile Toolkit Create an installable mobile web app with Angular CLI Make the App Installable with Web App Manifest App Shell Architecture Add an app shell component to the App Deep dive into Service Workers Add basic offline capabilities with Service Worker Adding Splash Screen Sending Push Notifications Questions and answers

  • 2,248