Web Workers with AngularJS

April 22, 2017 by Chris Sherman

Web Workers provide a means for processing data in background threads. Processing that occurs off the main thread is useful because JavaScript code executes in turns on a single thread. In the context of a web browser, while a turn is executing, no other processing occurs, and, if a turn runs long enough, the browser’s interface will be perceptibly frozen.

AngularJS

To incorporate Web Workers into an AngularJS application, we’ll create a service. With a service, we can reuse the same worker in multiple controllers throughout the application. Services are also a convenient place for isolating processing that happens outside the context of AngularJS.

Plunker

Below is a live example of Web Workers in an AngularJS application. Below the Plunker, I explain the key parts of the application.

Web Worker

The worker is defined inside the web-worker.js file. To conserve memory when passing messages to the worker, we stringify the message body. This means the worker must first parse the message data (line 7). In this implementation, directly after parsing the message, we get a reference to the unique identifier of the message (line 9). We uniquely identify each message in case multiple clients are calling the worker with different data.

Since the worker runs on a different thread from its client, the client cannot directly call a function inside the worker. To facilitate multiple, independent functions inside the worker, this worker contains a switch statement that uses the second parameter of the message to determine which worker function to call (data[1] on line 11).

Finally, the worker calls the matching function and passes the data in need of processing (data[2] on line 21). When the worker finishes processing, it stringifies the processed data and posts it back to the client with the MESSAGE_ID.

Web Worker Service

In web-worker.service.js, we expose a public method for calculating the compound annual growth rate (CAGR) from an array of values. This service establishes a reference to the worker (line 13) and posts messages to the worker inside the calculateCompoundAnnualGrowthRate function.

To keep track of calculations from different callers within the application, the calculateCompoundAnnualGrowthRate function passes a MESSAGE_ID parameter to the worker and back to its caller. The MESSAGE_ID is a unique identifier generated from the UUID Service.

As discussed in the worker code explanation, the second parameter is the name of the worker function we want to call, in this case, calculateCompoundAnnualGrowthRate. The third parameter is the data we need processed.

In the activate function of the service, we establish a listener for the worker object. This listener will call the onWebWorkerResponse handler whenever the worker indicates it has finished processing some data by posting a message.

Inside the onWebWorkerResponse function, we get the message identifier and the processed data, and use AngularJS’ $broadcast function to let any listeners know we have new data.

One thing to note — since the broadcast event does not trigger a digest cycle, we need to use the $apply function in order for values updated by the broadcasted data to update their bindings in the views.

Controller

Inside the activate function of app.controller.js, we ask the Web Worker Service to calculate the CAGR of the resolved data (fetched before initializing the controller in app.config.js). In this example, the resolved data is five years of home values. The Web Worker Service passes back a message identifier with which we can listen for a response once the data finishes processing. The listener is established on line 25.

When the listener receives a response, it calls the controller’s onMessageReceived function. This function updates the CAGR property in the view (home.html), formatting it as a percent in the process (line 29). Finally, we deregister the listener, since we don’t expect any more messages.

AngularJS Web Workers