Web Workers in Angular

Web Workers in Angular is simple in theory, but it requires a bit of tinkering to get them to feel like a part of the application. This post intends to explain a method which can make Web Workers in Angular (almost) seamless.

With the introduction of web workers web developers finally had the ability to allow long running, CPU intensive operations in the browser. These types of operations would otherwise have caused the GUI thread (the only thread up until this point) to block, and the GUI to freeze.

However the web worker syntax specification is a bit awkward, especially when combined with angularjs. To maintain complete backward compatibility and to protect developers from common parallel programming pitfalls the workers have a VERY limited exchange of information with the main browser thread. The main and web worker thread only communicate through message passing. All messages between the two are deep cloned (transferable objects can be used to minimize the overhead associated with the cloning). All this is well and good, it protects us from ourselves. But it makes for awkward programming.

Keeping the main and the web worker threads completely separated mean that resources loaded in the main thread is not loaded in the web worker thread automatically. In an angular application it means that if an angular app is using a web worker, the angular context is not automatically accessible in the web worker. If we do not load angular explicitly in the web worker it will only be capable of standard javascript. Whats worse is that a web workers needs to be loaded from a URL! What this normally means is that the developer specifies the javascript file that contains the web worker code like this:

var worker = new Worker('worker.js')

By using an addition to the web specification, which allows us to create object URL’s, we can get around the requirement of loading the web workers from separate files. This addition to the standard allows us to generate URL’s for snippets if blobs. Blobs can be pretty much anything, but we are interested in text that can be executed as javascript. A good explanation of this technique can be found here.

var blobURL = URL.createObjectURL(new Blob([
    "var i = 0;//web worker body"
    ], { type: 'application/javascript' }));
var worker = new Worker(blobURL);

We can not, and do not want to, get around the fact that the main and worker threads have separate contexts. But we can mitigate this inconvenience by this by leveraging the powerful dependency injection framework that angular ships with.

I have created an attempt at this here: https://github.com/FredrikSandell/angular-workers. angular-workers provide an angular service which takes a list containing dependencies, a function and returns a web worker, called AngularWorker, based on the functions source. The process is visualized in the sequence diagram below.

AngularWorkerCreation

Once the promise of an AngularWorker has been resolved the application is able to repeatedly use the initialized AngularWorker for heavy duty work. The angular worker communicates the result to the main thread through an angular promise. Within the web worker function we are ensured that an angular context exists and that the dependencies which we specified at initialization are present.

AngularWorkerExecution

A potential use case for this is to perform CPU intensive processing of large AJAX responses without locking the main GUI thread. This is the problem which first made me investigate this issue. The angular services which are used in the web worker can be handled as any other service and can therefore be tested as any other part of the angular application!

15 thoughts on “Web Workers in Angular

      • applebomb says:

        Hi FredrikSandell, Thank you for angular workers!
        I use webworkers to access server to get updated infomation by intervals.
        It works very well in browser (have already set CSP meta header in index.html)
        However, I have problem when running in virtual machine.

        The adb log is bellow:
        “XMLHttpRequest cannot load http://192.168.0.103:8000/api?method=high&token=1234567890. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘file://’ is therefore not allowed access.”

        Could you pls give me any advices for the problem? tks.

        my code about angular workers:
        controller(‘WarningsCtrl’, function($scope, $ionicPlatform, $window, $location, $ionicActionSheet, DB, Vulnerability, UserService, WorkerService, URL) {

        $scope.autoUpdate = function(arg){
        var workerPromise = WorkerService.createAngularWorker([‘input’, ‘output’, ‘$http’, function (input, output, $http) {
        //access from server
        var callback = function(){
        console.log(“url=”+input[‘url’]);
        $http.get(url)
        .success(function(response){
        //received response, send to main thread
        output.notify(response);
        });
        };

        setInterval(function(){
        callback();
        }, 5000);

        }]).then(function success(angularWorker){
        return angularWorker.run({token:$window.localStorage.token, url:URL.LatestVul});
        }, function error(err){
        console.error(err);
        }).then(function success(result){

        }, function error(err){
        console.error(err);
        }, function notify(update){
        console.log(update);
        //deal with update
        for(i in update){
        if(update[i].level==’high’ || update[i].level==’mid’){
        newvul=[update[i]];
        $scope.warnings = newvul.concat($scope.warnings);
        }
        }
        });
        }
        })

        • FredrikSandell says:

          Hi,

          The issue that you are seeing is not directly related to web workers. You have most likely loaded the script which uses the web worker code directly from a file. The web worker tries to make a HTTP request against an actual server (the IP address that you have listed in the error message). The browser (or web view in this case?) will reject this because the domain used to load the script-file directly from disk “file://” and the domain used when fetching the resource from the web worker “http://192.168.0.103:8000” differs.

          This error can be prevented either by making sure the script and any resources are loaded from the same domain (this is the preferred option if at all possible), or to present the browser with the correct HTTP header values explicitly allowing the use of this resource from the domain that the script is loaded from.

          • Sam says:

            Which browsers are the two of you using?

            From MDN

            There is currently disagreement among browsers vendors on what URIs are of the same-origin; Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0 / SeaMonkey 2.7) and later do allow data URIs and Internet Explorer 10 does not allow Blob URIs as a valid script for workers.

  1. Matjaz Jurecic says:

    Question:

    I am a bit new to web workers, so maybe this is a stupid question.

    I would like to use web worker when angularjs is creating html of data. I have a table, and I use ngRepeat=”tableData”, is it possible to use this lib, for when tableData changes, that processing and appending of the table is done in a web worker?

    Is that even possible to do with web workers?

    • FredrikSandell says:

      Hi Matjaz,

      Generally it is not a good idea to use web workers to populate a table. A web worker can not directly manipulate the DOM (which is what manipulating the table requires you to do). However if the data requires a lot of CPU bound processing before being inserted in to the table it can be a good idea to use web workers.

  2. Manik says:

    Hi,
    I am looking to use the web workers to upload some files over the network. The file size is ~2MB and there may be ~6 images in a lot. Is it a good idea to use webworkers. I tried with some of the directives available, but it uploads some of the files and misses some of them.

  3. Franklin Gerald X says:

    Hi. I have a question related to adding dependency. I am not able to access scope variable inside the workerpromise function.

    Eg. I am having a http call inside my worker code. I get the results. I need to pass the results to another function .

    Either I need to call the function inside the worker code or it should be returned to other function where i can manipulate the data.

    And also can you tell me how to add external dependency into webworker with an example. Like I want to add ngResource as a dependency

    Thanks in Advance
    Frank

  4. Chris Stephens says:

    This really is a good breakdown. My only problem with this design, is that its one message one response. Unless I read it wrong. Sometime you want to ask the worker to process a large list and message you as they they finish so you can start displaying things as they are processed. However if people are coming from a REST background then this would align with that paradigm.

    • FredrikSandell says:

      The implementation actually supports updates through the promise metod “notify”. This allows the worker to send updates to the main thread beforr it completes. It is even possible to imagine a use case in wich the worker never completes, but runs in the background for the entire duration of the program and updates the main thread through “notifys”.

Leave a Reply

Your email address will not be published. Required fields are marked *