Simple Server Polling in AngularJS Done Right
Plunker is here. Syncing with the server these days is much easier with libraries like socket.io and alike. But, even though there is no need to mess with…
Plunker is [here](http://plnkr.co/edit/Io7xt2?p=preview" target="_blank).
Syncing with the server these days is much easier with libraries like socket.io and alike. But, even though there is no need to mess with insane black magic like Comet or Flash-Remoting... - Sometimes all you need is just a simple polling.
In this example we'll see how to implement recursive polling to poll the server for updates. The example is in AngularJS 1.5.x ES5 but the approach is valid for any framework and/or plain javascript.
The key to recursive-polling, and what make it different from just polling, is that we wait for the response before we initiate the next interval. This means that even if we poll the server very fast and the server is slow to response we avoid issues like race-condition and overloading the server.
[Live demo](http://plnkr.co/edit/Io7xt2?p=preview" target="_blank)
var app = angular.module('plunker', ['ngAnimate']);
app.controller('MainCtrl', function($scope, $http, $timeout) {
var loadTime = 1000, //Load the data every second
errorCount = 0, //Counter for the server errors
loadPromise; //Pointer to the promise created by the Angular $timout service
var getData = function() {
$http.get('http://httpbin.org/delay/1?now=' + Date.now())
.then(function(res) {
$scope.data = res.data.args;
errorCount = 0;
nextLoad();
})
.catch(function(res) {
$scope.data = 'Server error';
nextLoad(++errorCount * 2 * loadTime);
});
};
var cancelNextLoad = function() {
$timeout.cancel(loadPromise);
};
var nextLoad = function(mill) {
mill = mill || loadTime;
//Always make sure the last timeout is cleared before starting a new one
cancelNextLoad();
loadPromise = $timeout(getData, mill);
};
//Start polling the data from the server
getData();
//Always clear the timeout when the view is destroyed, otherwise it will keep polling and leak memory
$scope.$on('$destroy', function() {
cancelNextLoad();
});
$scope.data = 'Loading...';
});
In the example we load some dummy data from [http://httpbin.org](http://httpbin.org" target="blank). We try to load it every 1000 milliseconds (1 second) but the server takes more than a second to respond. Using recursive polling saves us from some issues in this case.
When the server has an error (line 19) we generally wouldn't want to just stop the polling all together. It might be a just a temporary hiccup. We increase the timeout to the next load based on the count of response errors. nextLoad(++errorCount * 2 * loadTime);
So if the server issues are more serious we won't be hammering it. Eventually when it's fixed we'll go back to normal polling. errorCount = 0;
You can put this mechanism into a centralized service but always control it from the controllers. Start polling when the view is initialized and stop when the view is destroyed.
$scope.$on('$destroy', function() {
cancelNextLoad();
});
This way every view will be polling the server when it needs to and only then.
Comments (5)
Imported from the original blog
This is very helpful, but I'm a bit confused by loadPromise. It never seems to be set, and it is only passed in to $timeout.cancel. Are we supposed to set it to the promise returned by getData()?
Hi jwilhite,
the $timeout service in Angular returns a promise. When we want to cancel the timeout before it's called we use this promise $timeout.cancel(loadPromise).
The $http service also returns a promise, this is the promise you should use for getting your data.
The code contains an error. loadPromise should be assigned to the result of the $timeout action that we are performing, like this: --> loadPromise = $timeout(getData, mill);
Otherwise $timeout.cancel(loadPromise) is called on an unassigned variable and hence the task will never be canceled. Fix this and it will run very nicely :-)
Thanx, somehow I missed that one.
Nice article, helped me a lot to setup my polling routine. However, I was wondering if it's absolutely necessary to call cancelNextLoad() in the beginning of nextLoad()? Isn't the loadPromise already resolved one way or the other when nextLoad() is called?