简体   繁体   中英

Angular.js watch only on particular object property

Basically I want this http://plnkr.co/edit/3yfXbo1c0llO40HZ8WNP?p=preview but watch doesn't fire when I change something..

I know that this would have worked

$scope.$watch('stuff', function (newVal, oldVal) {
    console.log(oldVal, newVal);

}, true);

But since I want to do some summing up inside the watches and I don't want to unnecessarily loop thru or re-sum values that did not change..

//edit - note that the plnkr example is just an extraction from the actual app, where you can add and remove rows and much more, like modifying the total number(sum of somethings and somethingelses) from another input outside the ng-repeat..

I would not do a watch as depending on how large your array could be, might be very taxing. Instead I would just create a filter:

HTML:

Sum of something is: {{ stuff | sum:'something' }}<br/>
Sum of somethingelse is: {{ stuff | sum:'somethingelse' }}

Javascript:

.filter("sum", function(){
    return function(input, params){
        var totalSum = 0;
        for(var x = 0; x < input.length; x++){
            totalSum += input[x][params];
        }
        return totalSum;
    }
})

plnkr: http://plnkr.co/edit/p6kM3ampSuMXnwqcteYd?p=preview

I am smiling all over the place after I came up with this solution for your problem. Not only is this solution going to watch individual objects, it also is not going to do a complete loop through the rest of the objects for the summation.

http://plnkr.co/edit/oPWobcLLtJlxs5vjTYyc?p=preview

app.controller('MainCtrl', function($scope) {
  $scope.stuff = STUFF;
  var i;
  $scope.sumOfSomething = 0;
  $scope.sumOfSomethingElse = 0;

  for(i=0;i < $scope.stuff.length;i++ ){
    $scope.$watch('stuff['+i+'].something', function (newVal,oldVal) {
      // happens when the watch is registered.
      if(oldVal === newVal){
        $scope.sumOfSomething += +newVal;
        return;
      }
      if(newVal){
        $scope.sumOfSomething += + (newVal - oldVal);
      }
    });

    $scope.$watch('stuff['+i+'].somethingelse', function (newVal,oldVal) {
      // happens when the watch is registered. 
      if(oldVal === newVal){
        $scope.sumOfSomethingElse += +newVal;
        return;
      }

      if(newVal){
        $scope.sumOfSomethingElse += + (newVal - oldVal);
      }

    });  
  }
});

By the way I dont know how optimal this solution is going to be if you have a large number of objects in STUFF . Also, this wont work as is if the number of objects in STUFF is going to change. What we are basically doing is using the dirty checking loop of angular to do our summation for us.

Also notice the order of newVal and oldVal in the watch listener. Your order is wrong.

Since $watch() takes an angular expression you can use a custom watcher function. In your case the easiest way to watch for changes is simply to add up all the fields you're interested in then return a single number which is nice and efficient for angular and your sum will be ready to render.

var sumThings = function(obj, field) {
  var val = 0;
  obj.forEach(function(o) {
    val += parseInt(o[field]);
  });
  return val;
};

$scope.$watch(function() { return sumThings($scope.stuff, "something")}, function (newVal, oldVal) {    //console.log("watchExpression('something') fired!", oldVal, newVal);
  $scope.somethingSum = newVal;
});

$scope.$watch(function() { return sumThings($scope.stuff, "somethingelse")}, function (newVal, oldVal) {
  $scope.somethingelseSum = newVal;
});

Basically, by doing your summation you're doing your own dirty checking. It'll be more efficient than $watch(STUFF, true) if your objects are more complex than you've shown here.

Your updated plnkr : http://plnkr.co/edit/d7CvERu3XGkub4l6f1yw?p=preview

Well, if I got it, you just want to sum something with somethingelse of the inner objects when they change. And you don't want to loop through all the other inner objects if the change is in one of them.

Well, your $watch isn't firing for there is no stuff.property , and there is no patterns like stuff.*.something .

If you know the moment the objects gets pushed or pulled from the array, than you can apply a separately $scope.$watch(object, function(){}, true) before/after inserting each one to the array. If you don't know this moment, than you will need to $watch the entire array, and when a change happens, run all deregistration functions and $watch them all again, one by one.

Anyway, a loop in nowadays browsers is pretty fast, and except you're doing heavy calculations and you have millions of objects (what would have been a memory problem already) you'll probably be good with it. So, an effort to $watch them all separately is only necessary if you have really heavy calculations, that could comprise the system performance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM