Wednesday, October 28, 2020

Angular PWA with Workbox using webpack

 First create a simple angularjs project. For this we are using a simple example that will call W/S that will be dispalyed on the screen

Lets first look in to first aspect i.e.

1- We want to crate a New Project

The first step to create an Angular PWA is to upgrade the Angular CLI to the latest version:

npm install -g @angular/cli@latest

if you want to use the with latest features then below command

npm install -g @angular/cli@next

Execute below command to create a new AJS project with PWA feature.

ng new angular-workbox

now lets run the created AJs project using below command

PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-WorkBox\angular-pwa-workbox> ng serve

Now let change the project as per our need i.e. calling simple REST W/S and displaying the same on the screen.

if required install httpclient using below comamnd

npm i @angular/http

Note:- You can download the code from belwo given URL so that we can concentrate on real integration of workbox with our AJS application.

https://github.com/shdhumale/AngularJS-PWA-WorkBox.git

Now as our base project is ready lets install following workbox package that is going to be used in the projects using below commands.

npm install workbox-core workbox-precaching workbox-routing workbox-strategies workbox-cacheable-response workbox-expiration workbox-window workbox-background-sync workbox-navigation-preload workbox-broadcast-update

npm install workbox-cli -D
npm install webpack@4.41.5 webpack-cli@3.3.10 -D
npm install ts-loader -D
npm install workbox-build -D

Try to run the same offline and you will find you are not able to run the application offline, the reason we did not add service work file or manifest file etc.

confirm that workbox is added to your package using command as show below

PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-WorkBox\angular-pwa-workbox> workbox

workbox-cli is the command line interface for Workbox.

Usage:
$ workbox [options]

Commands:
wizard [--injectManifest]
Runs the configuration wizard, which will generate a
config file based on answers to questions.
By default the configuration will be tailored to the
generateSW use case.
If --injectManifest is provided, the wizard will ask
questions needed for the injectManifest use case.

generateSW [<path/to/config.js>] [--watch]
  Creates a new service worker file based on the options
  in the config file (defaults to workbox-config.js).
  If --watch is provided, the CLI will stay running, and will
  rebuild the service worker each time a file in the precache
  manifest changes.
  See https://bit.ly/wb-generateSW

injectManifest [<path/to/config.js>] [--watch]
  Takes an existing service worker file and creates a
  copy of it with a precache manifest "injected" into
  it. The precache manifest is generated based on the
  options in the config file (defaults to workbox-config.js).
  If --watch is provided, the CLI will stay running, and will
  rebuild the service worker each time a file in the precache
  manifest changes.
  See https://bit.ly/wb-injectManifest

copyLibraries <path/to/parent/dir>
  Makes a local copy of all of the Workbox libraries inside
  a version directory at the location specified. This is intended
  for developers using injectManifest who prefer using local,
  rather than CDN hosted, libraries.

Config file:
In 'generateSW' or 'injectManifest' mode, the config file should be a
JavaScript file, in CommonJS module format.
By default, a config file named workbox-config.js in the current
directory is assumed, but this can be overridden.

Examples:
$ workbox wizard
$ workbox wizard --injectManifest
$ workbox generateSW --watch
$ workbox injectManifest configs/workbox-dev-config.js
$ workbox copyLibraries build/

Now lets work on integration of workbox. We want to keep the workbox files in different folder.
Lets create a folder workbox in src. You can keep any name as you want and create following files inside it.

1- tsconfig.json
{
"compilerOptions": {
"typeRoots" : ["./typings"],
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"lib": [
"esnext", "webworker"]
},
"files": ["./swtemplate.ts"],
}

2- swtemplate.ts
import {skipWaiting, clientsClaim} from 'workbox-core';
import {precacheAndRoute, cleanupOutdatedCaches} from 'workbox-precaching';
import { CacheableResponsePlugin } from "workbox-cacheable-response";
import { ExpirationPlugin } from "workbox-expiration";
import { registerRoute, NavigationRoute } from "workbox-routing";
import { NetworkFirst, NetworkOnly, StaleWhileRevalidate, CacheFirst } from "workbox-strategies";
declare const self: ServiceWorkerGlobalScope;
skipWaiting();
clientsClaim();
cleanupOutdatedCaches();

precacheAndRoute(self.__WB_MANIFEST);
const DAY_IN_SECONDS = 24 * 60 * 60;
const MONTH_IN_SECONDS = DAY_IN_SECONDS * 30;
const YEAR_IN_SECONDS = DAY_IN_SECONDS * 365;
registerRoute(
'https://jsonplaceholder.typicode.com/posts',
new CacheFirst({
cacheName: "json-data",
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
new ExpirationPlugin({
maxAgeSeconds: YEAR_IN_SECONDS,
maxEntries: 30,
purgeOnQuotaError: true, // Automatically cleanup if quota is exceeded.
}),
],
}),
);

3- webpack.config.js

var path = require('path');
module.exports = {
mode: 'production',
entry: './swtemplate.ts',
output: {
path: path.resolve(__dirname, '.'),
filename: 'swtemplate.js'
},
module: {
rules: [{
test: /.ts$/,
loader: 'ts-loader'
}]
},
resolve: { extensions: [".js", ".ts"] }
};

4- workbox-config.js

module.exports = {
globDirectory: "../../dist/angular-workbox/",
//This is a good start for ngx but edit to match your app
globPatterns: [
"./",
".{png,svg,jpg,txt,gif,css,js,ico,eot,ttf,woff,json,html}", "index.html", "/.js"
//"assets/*.{png,svg,jpg,ico,gif}"
],
globFollow: true, // follow symlinks
globStrict: true, // fail on error
globIgnores: [
// Ignore Angular's ES5 bundles
**/*-es5.js*,
"sw.js"
],
// Don't need to cachebust angular files
dontCacheBustURLsMatching: new RegExp(".+.[a-f0-9]{20}..+"),
// Set large files to catch the angular vendor.js files. It is up to the developer to ensure the cache is not overwhelmed
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
swDest: "../../dist/angular-workbox/sw.js",
swSrc: "./swtemplate.js"
};

Now let check if our swtemplate.ts which is TS file can be converted to swtemplate.js using our webpack.config.js using this command
npx webpack swtemplate.ts --config webpack.config.js

C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-WorkBox\angular-workbox\src\workbox> npx webpack swtemplate.ts --config webpack.config.js

Also check this command
workbox injectManifest

This should work and should report the correct number of files to be cached and should generate sw.js in the correct place

Now execute the command as given below in sequence
PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-WorkBox\angular-workbox> ng build --prod

cd src/workbox

PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-WorkBox\angular-workbox\src\workbox> npx webpack swtemplate.ts --config webpack.config.js
Hash: b8efdd09cff992aa3c8b
Version: webpack 4.41.5
Time: 3190ms
Built at: 10/28/2020 2:15:56 PM
Asset Size Chunks Chunk Names
swtemplate.js 18.6 KiB 0 [emitted] main
Entrypoint main = swtemplate.js
[6] ./swtemplate.ts + 82 modules 198 KiB {0} [built]
| ./swtemplate.ts 974 bytes [built]
| + 82 hidden modules
+ 6 hidden modules

PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-WorkBox\angular-workbox\src\workbox> workbox injectManifest .\workbox-config.js
Using configuration from C:/Visual_Source_Code_WorkSpace_AngularJS/AngularJS-PWA-WorkBox/angular-workbox/src/workbox/workbox-config.js.
The service worker file was written to ../../dist/angular-workbox/sw.js
The service worker will precache 7 URLs, totaling 269 kB.

Also as we generally run our project using command ng serve but for PWA we are not going to use this command as ng serve run the applicaiton in cache and PWA will not work properly. For that we are going to use annother light weight server called as http-server you can install the same using below command.

PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-WorkBox\angular-pwa-workbox> npm i http-server

Run the angularjs project using below command

http-server -p 8081

Note:- If you are not able to open the application using above command follow below pathname

i.e. run this command
C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-WorkBox\angular-workbox\dist\angular-workbox>npx http-server -p 8181

You can see now you will be able to access the AngularJS site with PWA feature i.e. offline mode using service worker with workbox.

Note:- You can download the code from belwo given URL

https://github.com/shdhumale/AngularJS-PWA-WorkBox.git

Friday, October 23, 2020

Angular PWA with Service Worker

 AngularJS google Js Frame work can be easily integrated with PWA concept (again of Google) for allowing our application to run offline with Cache management.

In this blog we will try to understand how to integrate Service Worker concept in AngularJS project.

There are two possibilities
1- We want to crate a New Project
2- We already have an Existing Project in AJS and want to introduce PWA in it.

In both above example we will try to achieve the concept like Creating Angular PWA Application using the Angular CLI, Understanding and Updating (If project exist) How To Add Angular PWA Support Manually,Angular Service Worker runtime caching mechanism, Running and Understanding the PWA Production Build,Launching an Angular PWA in Production Mode, Deploying a new Application Version, Understanding Version Management and One-Click Install with the App Manifest

Lets first look in to first aspect i.e.

1- We want to crate a New Project

The first step to create an Angular PWA is to upgrade the Angular CLI to the latest version:

npm install -g @angular/cli@latest

if you want to use the with latest features then below command

npm install -g @angular/cli@next

Execute below command to create a new AJS project with PWA feature.

ng new angular-pwa-app

now lets run the created AJs project using below command

PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-NewProject-ServiceWorker\angular-pwa-app> ng serve

Also as we generally run our project in

ng serve

but for PWA we are not going to use this command as ng serve run the applicaiton in cache and PWA will not work properly. For that we are going to use annother light weight server called as http-server you can install the same using below command.

npm i http-server

Now we are ready to convert our existing AJs project into PWA. We can do the same using below command to do the same.

ng add @angular/pwa –project [name of project]

i.e. PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-NewProject-ServiceWorker\angular-pwa-app> ng add @angular/pwa –project angular-pwa-app

This command do follwing changes

1- Change 1: ‘@angular/pwa’ and ‘@angular/service-worker’ has now been added to package.json.

Note: If you did not find @angular/pwa in package.json dependencies then execute belwo command and recheck the same.

npm install @angular/pwa

There’s one other flag that is now enabled in you angular-cli.json(angular 2 and above) or angular.json(angular 6 and above) named “serviceworker” which is set to ‘true’.

serviceWorker: true flag make an application progressive by doing following below given jobs.
‘- It creates a file called ngsw-worker.js
‘- It captures run time config such us bundled files, images etc in to your ngsw.json

2- Change 2:
In our root module i.e. app.module.ts we can see CLI has included “ServiceWorkerModule”

ServiceWorkerModule.register(‘/ngsw-worker.js’, { enabled: environment.production })

“serviceworker: true” will create ngsw-worker.js while building the application. This is the reason we reference ‘ngsw-worker.js’ in ServiceWorkerModule registry.

Its also to be noted that enable property will set to true when the environment.production is set to true in your environment config file environment.prod.ts

i.e, The service worker module registers only and only if it is a production build.

In normal build environment.ts i.e. no prod build this flag is set to false.

The main task of this module is to register the angular service worker java script in the browser( if supported).

3- Change 3:
In index.html follwing line is added

i.e. it has the reference to the ‘manifest.json’ file which is very much essential when your wanted to add the app in to your home screen.

This file manifest.json has to be added in the assets folder in angular-cli.json(angular 2 and above) or angular.json(angular 6 and above) file.

Now lets build and run the application using below commands:-
PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-NewProject-ServiceWorker\angular-pwa-app> ng build –prod

now we can see the dist folder and inside that dist folder we have ngsw-worker.js created by cli for us.

Note:- We never update this file with our hand. It get updated automatically when ever you run ng build –prod command.

Now lets go inside out dist\angular-pwa-app folder and execute our project with http-server command given below

http-server -p 8081

Note:- If you are not able to open the application using above command follow below pathname

cd dist
cd project_name
npx http-server

i.e. run this command
npx http-server -p 8181

Now you might be knowing the thum rule of pwa i.e. your application can only accomodate PWA feature if you are running on HTTPS or it is on local host.

Now as we are running our application using IP address from http-server command

http://192.168.99.1:8181

we will not be able to see the service worker registration on chrome application tab alo no menifest entry as shown below

Run the browser with this url

http://loclahost:8181

Now lets go offline and refresh and check if we are able to run the application

We are successful in installing/integrating PWA in our AJS project.

Now lets make some changes in out project and add the functionality like

1- Dynamic Caching
For this concept we are going to call a JSON REST W/SAAJMetaFactory
for that we need to install below package
PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-NewProject-ServiceWorker\angular-pwa-app> npm i @angular/http

Also make some change in the following files

1- app.component.html

<app-post *ngFor="let post of posts" [content]="post.body" [title]="post.title"></app-post>

2- Add HttpClientModule in app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { HttpClientModule } from '@angular/common/http';
import { PostComponent } from './post/post.component';
@NgModule({
declarations: [
AppComponent,
PostComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

3- app.component.ts

import { Component, OnInit } from '@angular/core';
import { Post } from './post.model';
import { HttpClient} from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'angular-pwa-app';
posts: Post[] = [];
constructor(private http: HttpClient) {}
ngOnInit() {
this.http
.get('https://jsonplaceholder.typicode.com/posts')
.subscribe(fetchedPosts => (this.posts = fetchedPosts));
}
}

4- create this file post.model.ts

export interface Post {
title: string;
body: string;
id: number;
userId: number;
}


5- Create post.component.html

{{ title }}
{{ content }}

6- post.component.css

article {
padding: 1rem;
margin: 1rem auto;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
font-family: 'Oswald', sans-serif;
width: 30rem;
max-width: 80%;
}
h1 {
font-family: inherit;
font-weight: bold;
}

7- post.component.ts

import { Component, Input } from '@angular/core';
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.css']
})
export class PostComponent {
@Input() title: string;
@Input() content: string;
}

Now run following command this will refresh the code in our dist folder
PS C:\Visual_Source_Code_WorkSpace_AngularJS\AngularJS-PWA-NewProject-ServiceWorker\angular-pwa-app> ng build –prod

And finally run this command and run your application and check the out put.

Now lets go offline and make sure to go offline not by using check box of chrome .. as it very its behaviour..go offline by clicking wifi offline.

and check if you are able to see the REST call data on the screen and you will find you can see only hard code cache value and not the REST call data as we did not have yet done the cache of dynamic data lets do the same now.

As you can see we are able to see the static text angular-pwa-app but not the dynamic REST content and to do this we need to modified ngsw-config.json for that

"dataGroups": [
{
"name" :"postscalldata",
"urls": [
"https://jsonplaceholder.typicode.com/posts"
],
"cacheConfig": {
"maxSize": 5,
"maxAge": "10m",
"strategy": "freshness"
}
}
]

There are many option you can used
maxSize :- Will inform how many response we will cash and not the data
maxAge:- The age we need to keep the cache i.e. 5h , 10m or 1d
timeout:- If we are waiting for the response for 10 sec and if the response did not come then take the value from Cache.
Freshness:- Use cash if offline or take the data from betwork,
Performance:- It take maxAge account and decide weather to go to the back end and fetch the value or show

Now lets again run

ng build –prod

and

npx http-server -p 8181

and check now if we are able to access the data offline.

You can download the code from
https://github.com/shdhumale/AngularJS-PWA-NewProject-ServiceWorker.git