Using Web Workers with Angular 6 App generated by Angular-CLI 6
Suresh Patidar
Strategic IT Leader | Driving Digital Transformation, Innovation & Business Impact | Passionate about AI | Pre-Sales Specialist | Expert in UI Architecture & Full-Stack Development
This article talks about why Angular is popular among developers in building Enterprise-scale, high performance and responsive web applications. It also talks about different ways to use web workers in your angular applications. Finally it describes, step by step, how to modify an Angular 6 application generated by Angular-CLI 6 to use Web Worker by making use of angular builders.
Why Angular?
Angular become a popular and default choice for building Enterprise grade applications. With the improvements made to Angular by the Angular Core team in Angular 6, Angular has solidified its position as the ideal front-end framework for enterprise applications.
Based on standard conventions, familiar design patterns, strongly typed code, built-in data streaming, modular CLI and server-side rendering, enterprises can develop scalable applications across large teams that are highly performant and optimized for the server side and client side alike.
Why Performance of your web application is so critical?
Modern business use cases are very demanding and need lot of computation/processing capabilities in your web application and it should remain interactive and responsive to end user. To know how web worker can help building high performance and responsive web applications, please have a look at following presentation on SlideShare.
Using Web Workers with Angular
There are two common approaches for using web workers in your angular application:
- Running all the business logic of your app in an isolated web worker thread.
- Running just CPU intensive calculation logic in isolated web worker thread.
Both of these approaches has its own pros and cons and depending on the application use case you may choose any of the approach. To know more details about these approaches and detailed example of implementing the second approach, please have a look at following presentation:
Web Workers with Angular 6: Step by step
Preconditions
I am assuming that you have installed angular CLI 6.0 or higher globally. You have also installed NodeJS 8.9 or higher. If that's not the case, you may first want to install/upgrade these tools.
Create new Angular 6 App using CLI
Since Angular CLI 6 generates Angular 6 project, let's use following command to create simple application by running it in command window:
ng new angular-cli6-webworker
This will create a directory same as the name of the application. It will also download and install all the dependencies required for the application. Now let's change to the application directory by using command:
cd angular-cli6-webworker
Install "platform-webworker", "platform-webworker-dynamic" and "angular-cli-builders" modules required for building and running web worker.
npm install --save @angular/platform-webworker @angular/platform-webworker-dynamic
npm install -D angular-cli-builders
Change "src/app/app.module.ts" with the following code:
import { WorkerAppModule, WORKER_APP_LOCATION_PROVIDERS } from '@angular/platform-webworker';
import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
WorkerAppModule
],
providers: [
{
provide: APP_BASE_HREF,
useValue: '/'
},
WORKER_APP_LOCATION_PROVIDERS
],
bootstrap: [AppComponent]
})
export class AppModule { }
Change "src/main.ts" with the following code:
import { enableProdMode } from '@angular/core';
import { bootstrapWorkerUi, WORKER_UI_LOCATION_PROVIDERS } from '@angular/platform-webworker';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
bootstrapWorkerUi('webworker.bundle.js', WORKER_UI_LOCATION_PROVIDERS);
Create "src/workerLoader.ts" file containing the following code:
import './polyfills.ts';
import '@angular/core';
import '@angular/common';
import { platformWorkerAppDynamic } from '@angular/platform-webworker-dynamic';
import { AppModule } from './app/app.module';
platformWorkerAppDynamic().bootstrapModule(AppModule);
Modify "angular.json" to make use of angular builder to build the app using extra webpack config provided.
//... Other existing stuff ...
"architect": {
"build": {
"builder": "angular-cli-builders:custom-webpack-browser",
"options": {
"customWebpackConfig": {
"path": "./extra-webpack.config.js",
"replaceDuplicatePlugins": true,
"mergeStrategies": {
"optimization": "replace"
}
},
//... Other existing stuff ...
"serve": {
"builder": "angular-cli-builders:generic-dev-server",
//... Other existing stuff ...
Create "extra-webpack.config.js" in your application folder (angular-cli6-webworker)
const path = require('path');
const entryPoints = ["inline","polyfills","sw-register","styles","vendor","main"];
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
module.exports = {
target : "web",
"entry": {
"main": [
"./src/main.ts"
],
"polyfills": [
"./src/polyfills.ts"
],
"styles": [
"./src/styles.css"
],
"webworker": [
"./src/workerLoader.ts"
]
},
"output": {
"path": path.join(process.cwd(), "dist"),
"filename": "[name].bundle.js",
"chunkFilename": "[id].chunk.js"
},
"module": { },
"plugins": [
new HtmlWebpackPlugin({
"template": "./src/index.html",
"filename": "./index.html",
"hash": false,
"inject": true,
"compile": true,
"favicon": false,
"minify": false,
"cache": true,
"showErrors": true,
"chunks": "all",
"xhtml": true,
"excludeChunks": [
"webworker"
],
"title": "Webpack App",
"chunksSortMode": function sort(left, right) {
let leftIndex = entryPoints.indexOf(left.names[0]);
let rightIndex = entryPoints.indexOf(right.names[0]);
if (leftIndex > rightIndex) {
return 1;
}
else if (leftIndex < rightIndex) {
return -1;
}
else {
return 0;
}
}
}),
new AotPlugin({
"mainPath": "main.ts",
"entryModule": 'src/app/app.module#AppModule',
"hostReplacementPaths": {
"environments/environment.ts": "environments/environment.ts"
},
"exclude": [],
"tsConfigPath": "src/tsconfig.app.json",
"skipCodeGeneration": true
})
],
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
name: 'vendor',
test: /\/node_modules\//,
chunks: 'all',
priority: 0,
enforce: true,
},
},
},
},
};
We are all set to run our application in web worker. Try running command "npm start" to get started. Now let's make few more changes (Optional) in our app component to see and experience the benefits of running the business logic in web worker thread. Let's modify the "src/app/app.component.html" with following code:
<div class="container">
<div class="bird-container">
<div class="bird"></div>
</div>
</div>
<br>
<h4>Your application remains responsive and the bird keeps flying even if the CPU intensive processing is going on.</h4>
<button (click)="cpuIntensiveProcessing()">Start CPU Intensive Processing </button>
<h3>{{processingResult}}</h3>
Modify "src/app/app.component.css" with following code:
.container {
position: relative;
width: 100%;
height: 248px;
}
.bird {
background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/174479/bird-cells.svg);
background-size: auto 100%;
width: 166px;
height: 248px;
will-change: background-position;
animation-name: fly-cycle;
animation-duration: 1s;
animation-delay: -0.5s;
animation-timing-function: steps(10);
animation-iteration-count: infinite;
}
.bird-container {
position: absolute;
top: 0%;
left: 40%;
}
@keyframes fly-cycle {
100% {
background-position: -3600px 0;
}
}
Modify "src/app/app.component.ts" with following code:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'angular-cli6-webworker';
processingResult:string;
cpuIntensiveProcessing(d) {
let result = this.cpuIntensiveCalc(5000)
this.processingResult = "Total iterations done in 5 second duration :- " + result.iteration;
}
private cpuIntensiveCalc(duration: number) {
const before = new Date();
let count = 0;
while (true) {
count++;
const now = new Date();
if (now.valueOf() - before.valueOf() > duration) {
break;
}
}
return { iteration: count };
}
}
Here we mocked the CPU intensive processing by just entering into a while loop for given duration.
You can find the complete code of this example in Github:
Solutions Architect | GenAI | AI & ML | Cloud | Ex-IBM, Accenture, TCS |
6 年Thank you . Nice Article.
Lead Data & Analytics bei BROCKHAUS AG
6 年This article is really helpful. Thank you. But I have one problem. How do you publish the application. If I use ng build --prod and host the files on a server or run ng serve --prod I always get the error:?No NgModule metadata found for 'function(){}'. in webworker.bundle.js. I also tried the project on Github but get the same error. Do you have a solution for it?
UI / JS / CSS Expert - Full Stack
6 年This is awesome article. Just want to know How to use Webworker in our existing app ? We are working on a application, we can not create a new application now.?
Strategic IT Leader | Driving Digital Transformation, Innovation & Business Impact | Passionate about AI | Pre-Sales Specialist | Expert in UI Architecture & Full-Stack Development
6 年This article is very useful for people who have just started or planning to start development of new project in Angular and willing to use Web Workers. For existing projects, running all the business logic in web worker might not be a feasible approach because you have to first make your application compliant by removing all the direct references to window, document, DOM from your components and services. For such applications you can refer to the second approach (i.e. running some part of your business logic in web worker) mentioned in the article.