AngularJS watch an object property in an object array

All we need is an easy explanation of the problem, so here it is.

I have this data in my controller

    $scope.data = {
        home: {
            baseValue: "1",
            name: "home"
        },
        contact: {
            baseValue: "2",
            name: "contract"
        }
     // a lot more options
    };

with some html like this:

<section class="content row" ng-repeat="item in data">
   {{item.name}}
   ....
</section>

Now, I want to know when the baseValue is changed but because of I using a objects inside the data variable I can not watch the property in a simpler way.

I have tried something like this, but I have to loop all the array

$scope.$watch('data', function (newValue, oldValue, scope) {
 // some code to compare the tow arrays, line by line
}, true);

How can I do a simpler $watch to know only when the baseValue is changed?

Similar questions:

UPDATE 1

I could add an individual watch for each object to know when the baseValue is changed, but it won’t be nice if I had an n number of objects, not only a couple of objects like in this example

$scope.$watch('data.home', function (newValue, oldValue, scope) {
 // do some stuff with newvalue.baseValue
}, true);

$scope.$watch('data.contact', function (newValue, oldValue, scope) {
 // do some stuff with newvalue.baseValue
}, true);
... // Adds more individual `watch`

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Based on your question, you can use ngChange to watch changes of baseValue and trigger the function.

HTML

<section class="content row" ng-repeat="item in data">
    Name: {{item.name}} <br/>
    BaseValue: <input type="text" ng-model="item.baseValue" ng-change="baseValueChange(item.baseValue)"/>
</section>

Controller

$scope.baseValueChange = function(baseValue) {
    console.log("base value change", baseValue);
}

If you a more sophisticated version which can get oldValue and newValue, you can refer to this plunkr – http://plnkr.co/edit/hqWRG13gzT9H5hxmOIkO?p=preview

HTML

<section class="content row" ng-repeat="item in data">
    Name: {{item.name}} <br/>
    BaseValue: <input type="text" ng-init="item.oldBaseValue = item.baseValue" ng-model="item.baseValue" ng-change="baseValueChange(item.oldBaseValue, item.baseValue); item.oldBaseValue = item.baseValue"/>
</section>

Controller

$scope.baseValueChange = function(oldVal, newVal) {
    console.log("base value change", oldVal, newVal);
}

Method 2

You can watch an object attribute.
So you can do something like

for(var key in $scope.data) {
  if($scope.data.hasOwnProperty(key)) {
    $scope.$watch("data['" + key + "'].baseValue", function(val, oldVal) {
      // Do stuff
    });
  }
}

Not tested, but the idea is simple.

Method 3

In this kind of scenario there is no way to circumvent utilizing multiple watches, another way to do this is by utilizing $watchCollection to watch the array of object values, you can get this array using the Object.values function.

scope.$watchCollection(function() {
    return Object.values(obj);
}, function(newValues, oldValues) {
    // now you are watching all the values for changes!
    // if you want to fire a callback with the object as an argument:
    if (angular.isFunction(scope.callback())) {
        scope.callback()(obj);
    }
});

Method 4

I solved with this solution:

$scope.updateFields= function(){
    angular.forEach($scope.fields,function (value, key) {
        value.title = value.title.toLowerCase().replace(/\s+/g,'');
    })
};
$scope.$watch('fields', $scope.updateFields, true);

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply