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.
Static
Dynamic
AJAX
RWD
PWA
... Progressive Web App can be seen as an evolving hybrid of regular web pages (or websites) and a mobile application...
AppShell
Service
Worker
Manifest
Push
Make the website function offline
Increase online performance by reducing network requests for certain assets
Provide a customized offline fallback experience
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.
<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>
<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>
<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>
import { UniversalModule } from 'angular2-universal';
@NgModule({
bootstrap: [AppComponent],
imports: [
AppModule,
UniversalModule.withConfig({
...
}),
})
export class AppShellModule {}
platformUniversalDynamic()
.serializeModule(ShellModule)
.then(html => console.log(html));
# 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()
@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 {}
# 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>
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": {
"match": [{
"url": "/api",
"prefix": true,
"strategy": "fastest"
"invalidate": [...]
}]
}
}
Coming soon
import {ServiceWorkerModule} from '@angular/service-worker';
@NgModule({
declarations: [
DashboardComponent
],
exports: [
DashboardComponent
],
imports: [
...
ServiceWorkerModule
],
providers: []
})
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);
});
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(),
],
});
Backend
Push Service
Service worker
ngsw-manifest.json:
{
...,
"push": {
"showNotifications": true
}
}
manifest.webapp
{
...,
"gcm_sender_id": "12345"
}
# 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)), …
});
const webPush = require('web-push');
webPush.setGCMAPIKey(...);
webPush.setVapidDetails(
'mailto:salnikov@gmail.com',
'BHe82datF...',
's-zBxZ1Kl...'
);
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
});
}
}