darudude
darudude

Reputation: 541

Angular - Update scope when change in Factory data

I'm using a factory to poll a particular web service. This web service is used to update data any the factory. I initiate this factory in the main controller, and populate a scope variable through a factory function. The variable initializes correctly, and I get the right data on the screen, but I'm struggling on getting the data to bind automatically.

Edit for additional notes: The reason this code is in a Factory is that I plan on using the factory data across multiple views.

Here is what I have so far:

App.factory('metrics', function($http, $q, $timeout){
    var service;
    var users = [{laps:[]}];
    var updateMetrics = function(){
        //updates the users array in the factory
    };
    service.load = function (){
        var deferred = $q.defer();

        $http.get('http://www.example.com/api/random').success(function(data) {
            var temp_array = data.split("\n");
            updateMetrics(0, temp_array);
            deferred.resolve({status: 'good'});
            $timeout(service.load,3000);
        });
        return deferred.promise;
    };

    service.lastLapInfo = function(){
        var lastlap = [];
        for (var i=0; i<users.length;i++)
        {
            var lap = users[i].laps[users[i].laps.length-1];
            lastlap.push(lap);
        }
        return lastlap;
    };
    return service;
});

App.controller('mainController', function($scope, metrics) {
    metrics.load().then(function(response){
        $scope.$watch(function () { return metrics.lastLapInfo() }, function (newVal, oldVal) {
            if (newVal !=oldVal)
            {
                $scope.users=metrics.lastLapInfo();
            }
        });
    });
});

When I try the above, I get an error saying '10 $digest() iterations reached'. I don't see how that's possible, asI'm not calling the watch function multiple times.

Any suggestions (or other means to accomplish what I'm trying to do?)

Upvotes: 1

Views: 2027

Answers (1)

Walter Roman
Walter Roman

Reputation: 4779

If you're not 100% set on using $watch, a pattern that I prefer is to bind new instances of (not references to) modules to the current scope and keep the controllers strictly as components used for wiring together the project's views and models. This excludes the use of $watch, even for coordinating data across modules. I prefer to use $rootScope's $broadcast, $emit and $on methods within modules/factories (after passing in $rootScope as a service, which may or may not work for all situations, though it has for all that I've come across) rather than the comparatively sluggish $watch or $watchCollection methods. Using the latter makes me feel dirty inside... But I digress.

Would something like the following work in your situation?

App.factory('metrics', function($http, $q, $timeout){
    var service;
    service.users = [{laps:[]}];
    service.updateMetrics = function(){
        // updates the users array in the current instance of `metrics`
        // ex:
        // this.users = updatedMetrics;
        // don't do:
        // service.users = updatedMetrics;
    };
    service.load = function (){
        var deferred = $q.defer();

        $http.get('http://www.example.com/api/random').success(function(data) {
            var temp_array = data.split("\n");
            this.updateMetrics(0, temp_array);
            deferred.resolve({status: 'good'});
            $timeout(service.load,3000);
        }.bind(this));
        return deferred.promise;
    };

    service.lastLapInfo = function(){
        var lastlap = [];
        for (var i=0; i<this.users.length;i++)
        {
            var lap = this.users[i].laps[this.users[i].laps.length-1];
            lastlap.push(lap);
        }
        return lastlap;
    };
    return service;
});

App.controller('mainController', function($scope, metrics) {
    $scope.metrics = angular.copy(metrics);

    $scope.metrics.load();

});

By setting $scope.metrics = angular.copy(metrics), we are creating a new instance of metrics, rather than setting $scope.metrics as a reference to metrics ($scope.metrics = metrics). This has several benefits, including that you can now use multiple instances of the same module in the controller (ie $scope.foo = angular.copy(foo); $scope.bar = angular.copy(foo); since the objects bound to $scope are completely new objects, rather than references to the same module.

Another benefit is that the instance of metrics attached to $scope can be used to call methods on metrics which can allow any changes to metrics to automatically be applied to your controller's views. I frequently faced odd issues when trying to get this to work when not using angular.copy or $.extend, seemingly because changes to the referenced module attached to $scope were not always being registered.

Upvotes: 2

Related Questions