$watch an object

$watch an object

I want to watch for changes in a dictionary, but for some reason watch callback is not called.
Here is a controller that I use:
function MyController($scope) {
$scope.form = {
name: ‘my name’,
surname: ‘surname’
}

$scope.$watch(‘form’, function(newVal, oldVal){
console.log(‘changed’);
});
}

Here is fiddle.
I expect $watch callback to be fired each time name or surname is changed, but it doesn’t happen.
What is the correct way to do it?

Solutions/Answers:

Solution 1:

Call $watch with true as the third argument:

$scope.$watch('form', function(newVal, oldVal){
    console.log('changed');
}, true);

By default when comparing two complex objects in JavaScript, they will be checked for “reference” equality, which asks if the two objects refer to the same thing, rather than “value” equality, which checks if the values of all the properties of those objects are equal.

Per the Angular documentation, the third parameter is for objectEquality:

When objectEquality == true, inequality of the watchExpression is determined according to the angular.equals function. To save the value of the object for later comparison, the angular.copy function is used. This therefore means that watching complex objects will have adverse memory and performance implications.

Solution 2:

The form object isn’t changing, only the name property is

updated fiddle

function MyController($scope) {
$scope.form = {
    name: 'my name',
}

$scope.changeCount = 0;
$scope.$watch('form.name', function(newVal, oldVal){
    console.log('changed');
    $scope.changeCount++;
});
}

Solution 3:

Little performance tip if somebody has a datastore kind of service with key -> value pairs:

If you have a service called dataStore, you can update a timestamp whenever your big data object changes.
This way instead of deep watching the whole object, you are only watching a timestamp for change.

app.factory('dataStore', function () {

    var store = { data: [], change: [] };

    // when storing the data, updating the timestamp
    store.setData = function(key, data){
        store.data[key] = data;
        store.setChange(key);
    }

    // get the change to watch
    store.getChange = function(key){
        return store.change[key];
    }

    // set the change
    store.setChange = function(key){
        store.change[key] = new Date().getTime();
    }

});

And in a directive you are only watching the timestamp to change

app.directive("myDir", function ($scope, dataStore) {
    $scope.dataStore = dataStore;
    $scope.$watch('dataStore.getChange("myKey")', function(newVal, oldVal){
        if(newVal !== oldVal && newVal){
            // Data changed
        }
    });
});

Solution 4:

The reason why your code doesn’t work is because $watch by default does reference check. So in a nutshell it make sure that the object which is passed to it is new object. But in your case you are just modifying some property of form object not creating a new one. In order to make it work you can pass true as the third parameter.

$scope.$watch('form', function(newVal, oldVal){
    console.log('invoked');
}, true);

It will work but You can use $watchCollection which will be more efficient then $watch because $watchCollection will watch for shallow properties on form object. E.g.

$scope.$watchCollection('form', function (newVal, oldVal) {
    console.log(newVal, oldVal);
});

Solution 5:

As you are looking for form object changes, the best watching approach is to use
$watchCollection. Please have a look into official documentation for different performance characteristics.

Solution 6:

Try this:

function MyController($scope) {
    $scope.form = {
        name: 'my name',
        surname: 'surname'
    }

    function track(newValue, oldValue, scope) {
        console.log('changed');
    };

    $scope.$watch('form.name', track);
}