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.
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.
Static
Dynamic
AJAX
RWD
PWA
It has adapt to older browsers to deliver the best experience possible given the features that are available
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.
A well-designed website should use the URI to indicate the current state of the application. This will enable the web app to retain or reload its state when the user bookmarks or shares the app’s URL.
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.
The app should be able to receive notifications when the app is not running.
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.
Web app install banners give you the ability to let your users quickly and seamlessly add your web app to their home screen, making it easy to launch and return to your app.
A progressive web app can be installed on the device’s home screen, making it readily available.
HTTPS secures the connection between you and your users, ensuring your users information is protected and isn't tampered with.
Because a progressive web app has a more intimate user experience and because all network requests can be intercepted through service workers, it is imperative that the app be hosted over HTTPS.
It should work in areas of low connectivity or offline.
The app must be capable of starting offline and still display useful information.
Make the website function offline
Increase online performance by reducing network requests for certain assets
Provide a customized offline fallback experience
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 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
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);
});
}
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);
})
);
});
Update your SW file.
Your new service worker will be started and the install event will be fired.
At this point the old service worker is still controlling the current pages so the new service worker will enter a waiting state.
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.
Once your new service worker takes control, its activate event will be fired.
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);
}
})
);
})
);
});
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>Proper Application Shell</h3>
</div>
<div class="md-progress-bar"></div>
<div class="signup-form">...</div>
</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...",
...
}
}
}
import AngularSWPlugin from '@angular/service-worker/webpack';
webpack({
entry: 'index.html',
output: {...},
plugins: [
...,
new AngularSWPlugin()
]
});
{
...,
"dynamic": {
"match": [{
"url": "/api",
"prefix": true,
"strategy": "fastest"
"invalidate": [...]
}]
}
}
Coming soon
manifest.webapp
{
"name": "Hello Mobile",
"short_name": "Hello Mobile",
"icons": [
{
"src": "/android-chrome-36x36.png",
"sizes": "36x36",
"type": "image/png",
"density": 0.75
},
...
],
"theme_color": "#000000",
"background_color": "#e0e0e0",
"start_url": "/index.html",
"display": "standalone",
"orientation": "portrait"
}
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)), …
});
// Configure app to include module from @angular/service-worker
import {ServiceWorkerModule} 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
});
}
}