Progressive

Web Apps

using the Angular Mobile Toolkit

Maxim Salnikov

  • Google Developer Expert in Angular

  • Angular 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 can be seen as an evolving hybrid of regular web pages (or websites) and a mobile application...

PWA?

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

AppShell

Service

Worker

Manifest

Push

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

Involved APIs

  • Promises

  • Cache API

  • Fetch API

  • Notifications API

  • Push API

  • IndexedDB API

CanIUse?

Mobile Toolkit

Mobile Toolkit

Mobile Toolkit

No more

flag

--mobile

(for the moment)

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>Angular2Paris</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="">Angular2Paris</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

@angular/service-worker/plugins

Pre-fetching and caching

# First, install the Angular Service Worker
$ npm install --save @angular/service-worker

# Enable the SW registration + app shell in Angular CLI
$ ng set apps.0.serviceWorker=true

Pre-fetching and caching

ngsw-manifest.json:

{
  "static": {
    "urls": {
      "/index.html": "ae543...",
      "/app.js": "9ff18...",
      "/logo.png": "0e33a...",
      ...
    }
  }
}
/**
 * Gulp tasks that generates a basic Angular service worker manifest.
 */
gulpGenerateManifest()
gulpAddStaticFiles(files, options)
/**
 * Webpack plugin that generates a basic Angular service worker manifest.
 */
AngularServiceWorkerPlugin(manifestFile, manifestKey)

Dynamic Caching

{
  "optimizeFor": "performance", // try to respond as fast as possible
  "refreshAheadMs": 300000,     // refresh data if it's older than 5 min
  "strategy": "lfu",            // manage the cache by evicting the URLs using LFU
  "maxEntries": 1000,           // track 1,000 requests
  "maxAgeMs": 86400000          // consider entries stale after 1 day
}

Coming soon

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,
    "backgroundOnly": false
  }
}

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
    });

  }
}

Hold On!

Dank je!

@webmaxru

Maxim Salnikov

Made with Slides.com