0

In my application I want to have a self-updating data service, which is injected to the controllers:

app.factory("StatsService", function() {
    var service;
    service = {
        data: 0,
        init: function() {
            var self = this;
            setInterval(function() {
                self.data = Math.floor((Math.random() * 100) + 1);
            }, 1000);
        }
    };
    service.init();
    return service;
});

Here's an example controller:

app.controller("SourcesController", ['$scope', 'StatsService', function($scope, StatsService) {
    $scope.data = StatsService.data;

    $scope.$watch('data', function(val) {
        // this does not work either
    });
}

How I can make controller reflect service changes? I don't know whether I did something wrong with service or with controller, it just doesn't work.

acid
  • 2,099
  • 4
  • 28
  • 41

3 Answers3

2

Since the data property is a primitive int, value is copied into you scope.data. Now these are two seperate values, one stored in service and one in controller. So even if service data changes it would not affect controller data property.

To keep them in sync use object with sub properties.So data declaration, in service becomes

data:{value:0}

And where ever you bind use expression data.value

Chandermani
  • 42,589
  • 12
  • 85
  • 88
  • I changed `service = { data.value = 0 }` in my service and `$scope.data = StatsService.data.value` in my controller and it does not work. – acid Aug 21 '13 at 12:29
  • @acid Because you are essentialy going back to your original problem. If your controller have `data = StatsService.data` then the _reference_ will be the same and you will be able to access the value with `$scope.data.value` and the watch to `data.value`, otherwise you are, again, just copying the primitive value. – Simon Belanger Aug 21 '13 at 12:32
  • I don't know if I understood correctly, but I've changed `$scope.data = StatsService` and trying to grab `{{ data.value }}` in the view but it still returns initial value (0). Could you provide some code with your solution if i'm still doing something wrong? – acid Aug 21 '13 at 12:45
  • Use `$scope.data=StatsService.data`. Meanwhile, the question has been answered :) – Chandermani Aug 21 '13 at 13:23
1

The problem with your code is that the setInterval() is happening outside of the Angular world. If you are modifiyng things outside of the Angular world, then the framework does not know about the change. In that case you need to wrap your code into an $apply call to notify the framework about it.

app.factory("StatsService", ['$rootScope', function($rootScope) {
    var service;
    service = {
        data: 0,
        init: function() {
            var self = this;
            setInterval(function() {
                $rootScope.$apply(function(){
                    self.data = Math.floor((Math.random() * 100) + 1);
                });
            }, 1000);
        }
    };
    service.init();
    return service;
}]);

Usually you don't need to worry about that as Angular hooks into most things that can result in state changing. For instance, if you were using setTimeout instead of setInterval you could use Angular's $timeout service which than in turn notifies the framework behind the scenes so that you don't have to. It's actually a good idea to use setTimeout in favor for setInterval anyway in JavaScript as the calls of setInterval can be non deterministic.

I don't want to dive too deep into that topic but if you are interested in why to favor setTimeout over setInterval I recommend John Resig's post on the topic:

http://ejohn.org/blog/how-javascript-timers-work/

I also don't want to dive into the details of how Angular's databinding works as there are already some great answers on that topic here at SO:

Links:

How does data binding work in AngularJS?

Community
  • 1
  • 1
Christoph
  • 26,519
  • 28
  • 95
  • 133
0

Chandermani is right in his analysis. Another option than changing your service is to change your controller to have a function that returns the value instead. For example`

app.controller("SourcesController", ['$scope', 'StatsService', function($scope, StatsService) {
  $scope.data = function(newValue) {
    if(arguments.length === 0) return StatsService.data;
    StatsService.data = newValue;
  }

  $scope.$watch('data()', function(val) {
  });
}

The inconvenience is that you have to use data() in your binding to access the value and data(value) to change it.

Simon Belanger
  • 14,752
  • 3
  • 41
  • 35
  • I don't mind changing service, however as pointed [in this comment](http://stackoverflow.com/questions/18356840/self-updating-service-injected-to-the-controller#comment26950844_18357202) I don't think thats the case. Or I'm doing something wrong. :) – acid Aug 21 '13 at 12:59
  • 1
    @acid That's a separate problem. Christoph answers this in his post. – Simon Belanger Aug 21 '13 at 13:01