34

I'm building a web app using AngularJS. The app needs to poll a URL that returns JSON data and make that data available to any part of the app. From what I've read so far, my best bet is to create a service that handles the polling and keeps its own internal cache of the JSON data, and then inject the service into any part of the app that wants to consult that data. What I'm lost on is how to actually go about doing that. The closest example I've found is this question, but it appears to be creating a service that's manually called by a specific controller (which is itself tied to a given route), whereas I want something that persistently runs in the background of the app forever regardless of what part of the app is active. Is this doable, or am I taking the completely wrong approach?

Community
  • 1
  • 1
sslepian
  • 1,881
  • 5
  • 21
  • 26

3 Answers3

41

Here my solution:

app.factory('Poller', function($http, $timeout) {
  var data = { response: {}, calls: 0 };
  var poller = function() {
    $http.get('data.json').then(function(r) {
      data.response = r.data;
      data.calls++;
      $timeout(poller, 1000);
    });      
  };
  poller();

  return {
    data: data
  };
});

(calls just to show that polling is been done)

http://plnkr.co/edit/iMmhXTYweN4IrRrrpvMq?p=preview

EDIT: As Josh David Miller suggested in comments, dependency on this service should be added in app.run block to ensure polling is done from start:

app.run(function(Poller) {});

And also moved scheduling of next poll after previous call finished. So there would not be "stacking" of calls in case if polling hangs for a long time.

Updated plunker.

Valentyn Shybanov
  • 19,331
  • 7
  • 66
  • 59
  • 2
    +1 But he wants something always running, so also trigger it from an `app.run` block to ensure it's running from the start. I'd also check in the `poller` function to ensure the previous call to `$http` completed so they don't just stack up endlessly, but it seems there is no way to do that with the AngularJS promise implementation. – Josh David Miller Feb 18 '13 at 23:53
  • 1
    Thnx. Added both items into answer. And about stacking could be avoided by simply moving scheduling of next poll into "then" of previous call. Isn't it? – Valentyn Shybanov Feb 19 '13 at 09:28
  • 1
    Awesome! :-) I guess I was so focused on the original `q`'s promise method of `isFulfilled()` that `$q` doesn't implement that I missed the totally obvious. lol – Josh David Miller Feb 19 '13 at 18:45
  • I finally got around to needing this, and this answer was closest to the implementation I settled on. – sslepian Jun 18 '14 at 22:30
  • Great @ValentynShybanov I forked your coded and added intervals calls (every second, every 5 secs, etc), also you can stop and start the poller as you wish: http://plnkr.co/edit/EfsttAc4BtWSUiAU2lWf?p=preview – lito Mar 19 '16 at 13:23
21

Here is an angular poller service on Github that can be easily injected into your controller.

To install: bower install angular-poller.

Since you want to start a global polling service that runs in the background forever, you can do:

// Inject angular poller service.
var myModule = angular.module('myApp', ['poller']);

// The home/init controller when you start the app.
myModule.controller('myController', function($scope, $resource, poller) {

    // Define your resource object.
    var myResource = $resource(url[, paramDefaults]);

    // Create and start poller.
    var myPoller = poller.get(myResource);

    // Update view. Most likely you only need to define notifyCallback.
    myPoller.promise.then(successCallback, errorCallback, notifyCallback);
});

Now it will run in the background forever until you call myPoller.stop() or poller.stopAll().

If you want to use the callback data of this poller in other controllers, you can simply do:

myModule.controller('anotherController', function($scope, $resource, poller) {

    /* 
     * You can also move this to a $resource factory and inject it
     * into the controller so you do not have to define it twice.
     */
    var sameResource = $resource(url[, paramDefaults]);

    /* 
     * This will not create a new poller for the same resource
     * since it already exists, but will simply restarts it.
     */
    var samePoller = poller.get(sameResource);

    samePoller.promise.then(successCallback, errorCallback, notifyCallback);
});
Emma Guo
  • 531
  • 4
  • 9
  • It looks like a great library. Is there a sample app anything out there? – port5432 Apr 02 '14 at 21:35
  • @ardochhigh I'm planning to add a sample app soon. But before that happens, you can checkout the [readme](https://github.com/emmaguo/angular-poller/blob/master/README.md) page. :-) – Emma Guo Apr 02 '14 at 22:08
  • Thanks @Emma Guo the README page is quite detailed. I am fairly new to Angular and still struggle with some of the concepts. You library looks perfect for my requirements. Perhaps I will try to make a plunker for a proof of concept. I'll let you know. – port5432 Apr 03 '14 at 08:06
  • Would I be able to use this for polling promises? I have a bunch of services that I would like to poll a specific method from. Instead of making it a resource, I would like to just poll, for example: MyService.someMethod() on a regular interval. I could do a timeout, but if i get an HTTP error, which would be caught in an HTTPInterceptor, I would like to pause polling until the server is back online. – Patrick Apr 15 '14 at 14:18
0

I forked @ValentynShybanov's factory code and added intervals calls (every second, every 5 secs, etc), also you can stop and start the poller as you wish:

http://plnkr.co/edit/EfsttAc4BtWSUiAU2lWf?p=preview

app.factory('Poller', function($http, $timeout) {
  var pollerData = {
    response: {},
    calls: 0,
    stop: false
  };

  var isChannelLive = function() {
    $http.get('data.json').then(function(r) {
      if (pollerData.calls > 30 && pollerData.stop === false) { // call every minute after the first ~30 secs
        var d = new Date();
        console.log('> 30: ' + d.toLocaleString() + ' - count: ' + pollerData.calls);
        pollerData.calls++;
        $timeout(isChannelLive, 10000);
      } else if (pollerData.calls > 15 && pollerData.calls <= 30 && pollerData.stop === false) { // after the first ~15 secs, then call every 5 secs
        var d = new Date();
        console.log('> 15 & <= 30: ' + d.toLocaleString() + ' - count: ' + pollerData.calls);
        pollerData.calls++;
        $timeout(isChannelLive, 5000);
      } else if (pollerData.calls <= 15 && pollerData.stop === false) { // call every 1 second during the first ~15 seconds
        var d = new Date();
        console.log('<= 15: ' + d.toLocaleString() + ' - count: ' + pollerData.calls);
        pollerData.calls++;
        $timeout(isChannelLive, 1000);
      }

      pollerData.response = r.data;
    });

  };
  var init = function() {
    if (pollerData.calls === 0) {
      pollerData.stop = false;
      isChannelLive();
    }
  };
  var stop = function() {
    pollerData.calls = 0;
    pollerData.stop = true;
  };

  return {
    pollerData: pollerData, // this should be private
    init: init,
    stop: stop
  };
});
lito
  • 3,105
  • 11
  • 43
  • 71