AngularJS and web workers AngularJS and web workers javascript javascript

AngularJS and web workers


Communication with Web workers happens through a messaging mechanism. Intercepting these messages happens in a call back. In AngularJS, the best location to put a web worker is in a service as you duly noted. The best way to deal with this is to use promises, which Angular works amazingly with.

Here is an example of a webworker in a service

var app = angular.module("myApp",[]);app.factory("HelloWorldService",['$q',function($q){    var worker = new Worker('doWork.js');    var defer = $q.defer();    worker.addEventListener('message', function(e) {      console.log('Worker said: ', e.data);      defer.resolve(e.data);    }, false);    return {        doWork : function(myData){            defer = $q.defer();            worker.postMessage(myData); // Send data to our worker.             return defer.promise;        }    };});

Now whatever external entity that accesses Hello World service need not care about the implementation details of HelloWorldService - HelloWorldService could probably process the data over a web worker, over http or do the processing right there.

Hope this makes sense.


A very interesting question! I find the web worker specification a bit awkward (probably for good reasons, but still awkward). The need to keep the worker code in a separate file makes the intention of a service hard to read and introduces dependencies to static file URLs in your angular application code. This problem can be mitigated by using the URL.createObjectUrl() which can be used to create a URL for a JavaScript string. This allows us to specify the worker code in the same file which creates the worker.

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

The web worker spec also keeps the worker and main thread contexts completely separate to prevent situations were deadlocks and livelocks etc can occur. But it also means you won't have access to your angular services in the worker without some fiddling. The worker lack some of the things we(and angular) expect when executing JavaScript in the browser, like the global variable "document" etc. By "mocking" these required browser features in the worker we can get angular to run.

var window = self;self.history = {};var document = {    readyState: 'complete',    cookie: '',    querySelector: function () {},    createElement: function () {        return {            pathname: '',            setAttribute: function () {}        };    }};

Some features obviously won't work, bindings to the DOM etc. But the injection framework and for example the $http service will work just fine, which is probably what we want in a worker. What we gain by this is that we can run standard angular services in a worker. We can therefore unit test the services used in the worker just as we would with any other angular dependency.

I made a post which elaborates a bit more about this here and created a github repo which creates a service which implements the ideas discussed above here


I found a fully working example of web workers in Angular here

webworker.controller('webWorkerCtrl', ['$scope', '$q', function($scope, $q) {    $scope.workerReplyUI;    $scope.callWebWorker = function() {        var worker = new Worker('worker.js');        var defer = $q.defer();        worker.onmessage = function(e) {            defer.resolve(e.data);            worker.terminate();        };        worker.postMessage("http://jsonplaceholder.typicode.com/users");        return defer.promise;    }    $scope.callWebWorker().then(function(workerReply) {        $scope.workerReplyUI = workerReply;    });}]);

It uses promises to wait for the worker to return the result.