Creating a WebSocket in a WebWorker with Angular
In this post we'll cover using the Angluar CLI to create a Dedicated WebWorker and then open a WebSocket in the worker thread and post messages to and from the worker thread.
Why the heck would I want to do that? Great question. This is a useful technique if you have an application that is processing large and/or frequent events over a WebSocket. The intention is to reduce the load on the main UI thread and only post messages to the UI once the data has been handled in the worker thread.
Set Up
Lets start by creating out app.
ng new angular-websocket-in-worker
Angular makes it very easy to create a WebWorker. The following command will create our worker stub and update our config to work with WebWorkers.
Add WebWorker
ng generate web-worker angular-websocket-on-worker
If you run git status
you will see that angular.json and tsconfig.json have been modified and two new files created, our worker file and a tsconfig for the worker.
Our angular.json file has been updated to include the webWorkerTsConfig build option which references the newly created tsconfig.worker.json.
The tsconfig.worker.json includes the newly created worker file and lets the Typescript compiler know it's using lib 'webworker'.
Our existing tsconfig.json file has been updated to include a new path for the Web Worker config.
Finally, the actual worker file is created. On the first line of this file there is a rather strange entry:
/// <reference lib="webworker" />
addEventListener('message', ({ data }) => {
connectWebsocket();
});
This is a TypeScript Triple-Slash Directive (see documentation below). This informs TypeScript to import type definitions for use in this file.
Add WebSocket to WebWorker
First we need to create our WebSocket and add some event handlers. An enum (WebSocketEvent) is defined to allow us to reference the WebSocket events we'll use:
- onopen
- onmessage
- onclose
- onerror
We'll use the handy websocket.org endpoint for our WebSocket.
/// <reference lib="webworker" />
import { WebSocketEvent } from './index';
const socket = new WebSocket('wss://echo.websocket.org');
addEventListener('message', ({ data }) => {
if (data.event === WebSocketEvent.OPEN) {
connectWebsocket();
}
if (data.event === WebSocketEvent.CLOSE) {
socket.close();
}
if (data.event === WebSocketEvent.MESSAGE) {
socket.send(data.message);
}
});
function connectWebsocket() {
socket.onmessage = (message: MessageEvent) => {
postMessage({ event: WebSocketEvent.MESSAGE, message: message.data});
}
socket.onclose = (event) => {
postMessage({ event: WebSocketEvent.CLOSE });
}
socket.onerror = (event) => {
postMessage({ event: WebSocketEvent.ERROR });
}
socket.onopen = (event) => {
postMessage({ event: WebSocketEvent.OPEN });
}
}
Initialize WebWorker and Open WebSocket
We'll use a click event in our template to call a method that will initialize the WebWorker and open the WebSocket connection.
// app.component.ts
openWebSocket() {
if (typeof Worker !== 'undefined') {
this.worker = new Worker('./app.worker', { type: 'module' });
this.worker.onmessage = ({ data }) => {
if (data.event === WebSocketEvent.OPEN) {
this.state = WebSocketEvent.OPEN;
}
if (data.event === WebSocketEvent.CLOSE) {
this.state = WebSocketEvent.CLOSE;
}
if (data.event === WebSocketEvent.MESSAGE) {
this.receivedMessage = data.message;
}
};
this.worker.postMessage({ event: WebSocketEvent.OPEN });
} else {
// Worker not supported!!!
}
}
Once the connection is open we can now add some text and send our message to the echo endpoint.
We now have an open WebSocket connection running on a Worker thread.
HOT TIP: Worker threads run in their own context so if you want to see console output from a worker you'll need to change the console level in devtools.
Finally
Angular makes working with WebWorkers very easy, they should only be used where there's a good reason i.e. handling large payloads and/or running script intensive tasks. A good way to see if they may be needed is to profile your application using devtools and look for any long tasks (highlighted in red) related to your applications functions.
The code from this post can be found here.