Rick Eyre
Rick Eyre

Reputation: 2475

$watchCollection isn't working correctly

I'm currently trying to bind a value to a scope and then watch it, but it doesn't seem to be working correctly. The $watch is never fired. Service.info object and isn't apart of the scope or anything. The service is also the one causing the changes to info and not the Controller with the $watch. Might that causing the issue?

// Service code
function() {
    var info = {
        prop: false
    };

    window.addEventListener('offline', function() {
        info.prop = !info.prop;
    }, false);

    return {
        info: info
    };
}

// Controller code
$scope.info = Service.info;
$scope.$watchCollection('info', function(newValue) {
    if (newValue.prop) {
        doSomething();
    }
});

Upvotes: 1

Views: 2058

Answers (2)

Rick Eyre
Rick Eyre

Reputation: 2475

So I figured it out. Turns out that the Controller I was calling this in was nested in an ng-if. Therefore, if the ng-if equated to false and didn't render the DOM element that the Controller was associated with, the $scope I was watching on didn't actually exist.

Upvotes: 1

Pete
Pete

Reputation: 3254

Cause

The $watchCollection is only a shallow watch. So watching info will only look for changes in info's properties. If no properties are added or changed, then the watch will not be triggered. To fix this you would need to watch info.prop or you could use a $watch and set the flag to use a deep watch - however this will be less performant.

$watchCollection documentation

Shallow watches the properties of an object and fires whenever any of the properties change (for arrays, this implies watching the array items; for object maps, this implies watching the properties). If a change is detected, the listener callback is fired.

Demo

jsFiddle

In this demo you can see that by manipulating info, the $watchCollection on info is triggered. Manipulating prop, the $watchCollection on info.prop is triggered.

HTML

<div ng-app ng-controller="ctrl">
    <pre>
        {{ info | json }}
    </pre>
    <button ng-click="change()">
        Change!
    </button>
    <button ng-click="addProp()">
        Affect info!
    </button>
</div>

Controller

function ctrl($scope) {
    $scope.info = {
        prop: {}
    };

    $scope.change = function() {
        $scope.info.prop["dog" + Math.random()] = Math.random();
    }

    $scope.addProp = function() {
        $scope.info["cat" + Math.random()] = Math.random();
    }

    $scope.$watchCollection('info.prop', function(value) {
        if (value) {
            console.log("info.prop has changed!");
        }
    });

    $scope.$watchCollection('info', function(value) {
        if (value.prop) {
            console.log("info has changed!");
        }
    });

    $scope.$watch('info', function(value) {
        if (value) {
            console.log("deep watch triggered");   
        }
    }, true);
};

Upvotes: 5

Related Questions