5

I'm using two $watches on my controller that are supposed to take an eye at these two objects:

$scope.gastos = {
    name: "Gastos mensuales",
    data: [0,0,0,0,0,0,0,0,0,0,0,0],
    labels: ["Enero", "Febrero", "Marzo", "Abril", "Mayo",
                "Junio", "Julio", "Agosto", "Septiembre", "Octubre",
                "Noviembre", "Diciembre"]
};

$scope.ganancias = {
    name: "Ganancias mensuales",
    data: [0,0,0,0,0,0,0,0,0,0,0,0],
    labels: ["Enero", "Febrero", "Marzo", "Abril", "Mayo",
                "Junio", "Julio", "Agosto", "Septiembre", "Octubre",
                "Noviembre", "Diciembre"]
};

Two charts (from the Charts.js and angular-charts plugins) read data from them. I've put the charts in custom directives that receive the data from an attribute, and they're working properly.

The problem is, that I want to create another chart who reads another object that is equal to these, but calculates it's data in this method:

function calcularBeneficios(){
 var data = [];
 for(var i=0;i<12;i++){
    data[i] = $scope.ganancias.data[i] - $scope.gastos.data[i];
 }
    console.log("FUNCTION DATA: "+data);
 return data;
}

These are the watches (I tried both watching the object and the object.data variable):

$scope.$watch("gastos", function(){
  $scope.beneficios.data = calcularBeneficios();
  console.log("SCOPE: "+$scope.beneficios.data);
});

$scope.$watch("ganancias", function(){
  $scope.beneficios.data = calcularBeneficios();
  console.log("SCOPE: "+$scope.beneficios.data);
});

This doesn't work. You see all the console.logs? I only see the "SCOPE" console.log, once (not even twice for ganancias). When I change the data in some inputs that are bound to these two objects, everything works (the charts get real-time updated) but the beneficios chart doesn't, aswell these watches just don't work.

Am I doing something wrong at these two watches?

Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
Zerok
  • 1,323
  • 1
  • 24
  • 56
  • in the `console.log("SCOPE:")`, is `$scope.beneficios.data` being printed out with its updated value? – Rhumborl Feb 25 '16 at 09:37
  • 3
    Possible duplicate of [$watch not being triggered on array change](http://stackoverflow.com/questions/15363259/watch-not-being-triggered-on-array-change) – mido Feb 25 '16 at 09:46

2 Answers2

5

The problem is is that you are watching the top-level gastos and ganancias objects. There are two potential issues with this:

  1. As the docs for $watch say, by default, normal JavaScript inequality is used to determine if an object has changed (x !== y). As the object itself is the same object, you are just changing data within it, this will always be true, so the $watch is not fired.

  2. You can get around 1. by setting the objectEquality flag to true ($scope.$watch('x', function(){}, true)). This then does a deep comparison and should pick up differences in the data array. However this is much more performance intensive.

As you are just using the data array in each object, you can simply use $scope.$watchCollection on gastos.data. This is cleaner and faster.

Here is a working snippet demonstrating this:

angular.module("myApp", []).controller('myCtrl', function($scope) {

  // same code
  function calcularBeneficios() {
    var data = [];
    for (var i = 0; i < 12; i++) {
      data[i] = $scope.ganancias.data[i] - $scope.gastos.data[i];
    }
    console.log("FUNCTION DATA: " + data);
    return data;
  }

  
  $scope.beneficios = {
    name: "Beneficios mensuales",
    data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    labels: ["Enero", "Febrero", "Marzo", "Abril", "Mayo",
      "Junio", "Julio", "Agosto", "Septiembre", "Octubre",
      "Noviembre", "Diciembre"
    ]
  };
  
  $scope.gastos = {
    name: "Gastos mensuales",
    data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    labels: ["Enero", "Febrero", "Marzo", "Abril", "Mayo",
      "Junio", "Julio", "Agosto", "Septiembre", "Octubre",
      "Noviembre", "Diciembre"
    ]
  };

  $scope.ganancias = {
    name: "Ganancias mensuales",
    data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    labels: ["Enero", "Febrero", "Marzo", "Abril", "Mayo",
      "Junio", "Julio", "Agosto", "Septiembre", "Octubre",
      "Noviembre", "Diciembre"
    ]
  };

  // watch the array collection in gastos.data 
  $scope.$watchCollection("gastos.data", function() {
    $scope.beneficios.data = calcularBeneficios();
    console.log("SCOPE: " + $scope.beneficios.data);
  });

  // watch the array collection in ganancias.data 
  $scope.$watchCollection("ganancias.data", function() {
    $scope.beneficios.data = calcularBeneficios();
    console.log("SCOPE: " + $scope.beneficios.data);
  });
  
  
  // testing functions

  $scope.changeGastos = function() {
    $scope.gastos.data[4] = 10;
  }
  
  $scope.changeGanancias = function() {
    $scope.ganancias.data[0] = 40;
  }
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>

<div ng-app="myApp" ng-controller="myCtrl">
  <h3>beneficios.data</h3>
  <ul>
    <li ng-repeat="i in beneficios.data track by $index">{{i}}</li>
  </ul>
  <button ng-click="changeGastos()">Change Gastos</button>
  <button ng-click="changeGanancias()">Change Ganancias</button>
</div>
Rhumborl
  • 16,349
  • 4
  • 39
  • 45
3

Use $watchCollection instead of $watch to watch for a collection.

Or you can also watch like this:

$scope.$watch("gastos + ganancias", function(){
  $scope.beneficios.data = calcularBeneficios();
  console.log("SCOPE: "+$scope.beneficios.data);
});
Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
  • Thank you! Both work, but only if I aim to gastos.data and ganancias.data. Which should I use, $watchCollection and place a watch for each of them, or that simple $watch? – Zerok Feb 25 '16 at 09:43
  • 1
    You should use `$watchCollection` instead because it should be less expensive than the deep `$watch`. http://www.bennadel.com/blog/2566-scope-watch-vs-watchcollection-in-angularjs.htm – Shashank Agrawal Feb 25 '16 at 09:49
  • You can get more detailed usage of `$watch` & `$watchCollection` at http://stackoverflow.com/a/29189252/2405040. It now depends on your need. – Shashank Agrawal Feb 25 '16 at 09:56
  • 1
    BTW `$scope.$watchGroup(["gastos", "ganancias"])` is cleaner than `$watch("gastos + ganancias")` – Rhumborl Feb 25 '16 at 09:59