Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS : watching a particular property in an array of objects for changes in the property's value

In my custom directive, I'm adding elements to the DOM based on the number of objects in my datasource array. I need to watch a specific property in each object. As I add these elements to the DOM, I want to set up a $watch on the checked property of each object in the toppings array, but it's not working, and I don't know why. I set up a breakpoint inside the function that should be invoked when the property changes from true to false or false to true, but that function is never invoked. Is the reason obvious? I'm just learning Angular, so I could easily be making a stupid error.

$scope.bits = 66;  (i.e. onions and olives)


$scope.toppings = [
    { topping: 1, bits: 2, name: 'onions' },
    { topping: 2, bits: 4, name: 'mushrooms' },
    { topping: 3, bits: 8, name: 'peppers' },
    { topping: 4, bits: 16, name: 'anchovies' },
    { topping: 5, bits: 32, name: 'artichokes' },
    { topping: 6, bits: 64, name: 'olives' },
    { topping: 7, bits: 128, name: 'sausage' },
    { topping: 8, bits: 256, name: 'pepperoni' }
     ]

Each object in the model gets a new checked property which will be true or false.

NOTE: the object array will at most contain a dozen or so items. Performance is not a concern.

link: function link(scope, iElement, iAttrs, controller, transcludeFn) {


  <snip>

 // At this point  scope.model refers to $scope.toppings. Confirmed.

  angular.forEach(scope.model, function (value, key) {

     // bitwise: set checked to true|false based on scope.bits and topping.bits

     scope.model[key].checked = ((value.bits & scope.bits) > 0);  


     scope.$watch(scope.model[key].checked, function () {
         var totlBits = 0;
         for (var i = 0; i < scope.model.length; i++) {
            if (scope.model[i].checked) totlBits += scope.model[i].bits;
         }
          scope.bits = totlBits;
      });

   });

     <snip>
like image 579
Tim Avatar asked Oct 24 '14 17:10

Tim


2 Answers

Array of Objects:

$scope.toppings = [
   { topping: 1, bits: 2, name: 'onions' },
   { topping: 2, bits: 4, name: 'mushrooms' },
   { topping: 3, bits: 8, name: 'peppers', checked:undefined /*may be*/ }
];

Watch using AngularJs $WatchCollection:

Instead of monitoring objects array above, that can change for any property in the object, we will create an array of properties of the elements for which we are watching the collection (.checked).

We filter the array's elements to only monitor those elements which have .checked defined and map that to an array for angular watchCollection.

When a change fires, I will compare the old and new arrays of (.checked) to get exact changed element using lodash difference method.

 $scope.$watchCollection(

                // Watch Function
                () => (
                   $scope
                     .toppings
                     .filter(tp => tp.checked !== undefined)
                     .map(tp => tp.checked)
                 ),

                // Listener
                (nv, ov) => {
                    // nothing changed
                    if(nv == ov || nv == "undefined") return;

                    // Use lodash library to get the changed obj
                    let changedTop = _.difference(nv,ov)[0];
                
                    // Here you go..
                    console.log("changed Topping", changedTop);
            })
like image 146
ahmadalibaloch Avatar answered Dec 01 '22 01:12

ahmadalibaloch


You use MAP to collect all of the property values you need + convert them into a small string representation (in this case 1 and 0) and then join them together into a string that can be observed.

A typescript example:

        $scope.$watch(
            () => this.someArray.map(x => x.selected ? "1" : "0").join(""),
            (newValue, oldValue, scope) => this.onSelectionChanged(this.getSelectedItems()));
like image 41
Peter Morris Avatar answered Nov 30 '22 23:11

Peter Morris