52

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`
Community
  • 1
  • 1
Coyolero
  • 2,353
  • 4
  • 25
  • 34
  • You want to only know when `baseValue` has changed but you don't want to add an individual watch for it? – lucuma Jul 22 '14 at 00:28
  • About your update 1, can you define "not nice" ? – Jerska Jul 22 '14 at 00:34
  • how about using [property descriptors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), and falling back to `*Timeout`'s? – public override Jul 22 '14 at 00:40
  • Do you need to know old value? Or you just need to watch for changes? – hutingung Jul 22 '14 at 00:40
  • Or $watchGroup ? A variant of $watch() where it watches an array of watchExpressions. If any one expression in the collection changes the listener is executed. – hutingung Jul 22 '14 at 01:05

4 Answers4

35

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);
}
hutingung
  • 1,800
  • 16
  • 25
  • This worked for me, but isn't true that by using `ng-change` the performance could be impacted? This is because the function would be executed not only when the value is changed. For that reason it is better to use a `wath` to ensure that the function is executed only when the data is updated. Is that correct? – Coyolero Jul 23 '14 at 17:13
  • I think not much different compare to watch. You might want to compare number of listener register in between ng-change and watch. I think is the same. – hutingung Jul 23 '14 at 17:38
  • I agree with +Coyolero. The solution that @Jerska offered fits the question better in my case – Elias Nov 04 '14 at 01:13
  • I dont think this is a good answer as it will only be triggered on updates from the input control and not from code – csharpsql May 23 '16 at 09:36
16

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.

Jerska
  • 11,722
  • 4
  • 35
  • 54
  • 1
    I just started learning AngularJS two days ago. I love it so far. This solution is exactly what I need. I noticed that the `ng-change` directive only works with values changed by interacting with bound inputs (checkboxes in my case). I want do something when the array's elements baseValues change, regardless of whether they changed from bound inputs or by programmatically toggling them. – Elias Nov 04 '14 at 01:12
  • This should be achieved by using `$scope->$apply()` after a modification. (See https://docs.angularjs.org/api/ng/type/$rootScope.Scope) – Jerska Nov 10 '14 at 16:48
  • My changes are already happening within the angular context, so `$scope.$apply()` is not allowed (it says `$digest()` is already happening). However, I'm a little more experienced with angular now and the problem may have been related to scopes and asynchronous calls. – Elias Nov 12 '14 at 22:55
  • 1
    If the items are coming and going, you should also remove the stale watchers: `var w = $scope.$watch(...);` and later just call `w();` to unregister. – z0r Nov 12 '14 at 23:45
  • @Elias I've used a lot AngularJS in the past months and I am still not really sure how the `digest` cycles work. But I believe with your comment that you don't have this problem anymore ? – Jerska Nov 17 '14 at 18:32
  • 1
    ```$scope.$watch("data[" + key + "].baseValue" .....)``` didnt work for me, but ```$scope.$watch("data['" + key + "'].baseValue" .....)``` did – parliament May 17 '15 at 04:35
  • 2
    Just a safety thing, I'd do `"data[" + JSON.stringify(key) + "].baseValue"` instead of `"data['" + key + "'].baseValue"` – Hashbrown Nov 08 '19 at 02:20
  • 1
    @Hashbrown Agreed. I feel like if you're not aware of this trick to escape strings, it might end up more confusing, so I won't change the answer, but hopefully your comment will be upvoted and easily seen. – Jerska Nov 14 '19 at 09:38
6

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);
    }
});
svarog
  • 9,477
  • 4
  • 61
  • 77
2

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);
Ehsan Ali
  • 1,362
  • 5
  • 25
  • 51