$ vigilando cambios de datos en una directiva angular

132

¿Cómo puedo activar una $watchvariable en una directiva angular al manipular los datos en su interior (por ejemplo, insertar o eliminar datos), pero no asignar un nuevo objeto a esa variable?

Tengo un conjunto de datos simple actualmente cargado desde un archivo JSON. Mi controlador angular hace esto, además de definir algunas funciones:

App.controller('AppCtrl', function AppCtrl($scope, JsonService) {
    // load the initial data model
    if (!$scope.data) {
        JsonService.getData(function(data) {
            $scope.data = data;
            $scope.records = data.children.length;
        });
    } else {
        console.log("I have data already... " + $scope.data);
    }

    // adds a resource to the 'data' object
    $scope.add = function() {
        $scope.data.children.push({ "name": "!Insert This!" });
    };

    // removes the resource from the 'data' object
    $scope.remove = function(resource) {
        console.log("I'm going to remove this!");
        console.log(resource);
    };

    $scope.highlight = function() {

    };
});

Tengo una <button>que llama correctamente la $scope.addfunción, y el nuevo objeto se inserta correctamente en el $scope.dataconjunto. Una tabla que configuré se actualiza cada vez que presiono el botón "Agregar".

<table class="table table-striped table-condensed">
  <tbody>
    <tr ng-repeat="child in data.children | filter:search | orderBy:'name'">
      <td><input type="checkbox"></td>
      <td>{{child.name}}</td>
      <td><button class="btn btn-small" ng-click="remove(child)" ng-mouseover="highlight()"><i class="icon-remove-sign"></i> remove</button></td>
    </tr>
  </tbody>
</table>

Sin embargo, una directiva que configuré para mirar $scope.datano se dispara cuando todo esto sucede.

Defino mi etiqueta en HTML:

<d3-visualization val="data"></d3-visualization>

Que se asocia con la siguiente directiva (recortada para la cordura de la pregunta):

App.directive('d3Visualization', function() {
    return {
        restrict: 'E',
        scope: {
            val: '='
        },
        link: function(scope, element, attrs) {
            scope.$watch('val', function(newValue, oldValue) {
                if (newValue)
                    console.log("I see a data change!");
            });
        }
    }
});

Recibo el "I see a data change!"mensaje al principio, pero nunca después cuando presiono el botón "agregar".

¿Cómo puedo activar el $watchevento cuando solo estoy agregando / quitando objetos del dataobjeto, sin obtener un conjunto de datos completamente nuevo para asignar al dataobjeto?

Mono Armario Malvado
fuente
55
Solo un consejo rápido si newValue alguna vez es igual a 0, falso "" o cualquier otra cosa como esa "¡Veo un cambio de datos!" no disparará Por ejemplo, el valor original era verdadero, el nuevo valor es falso. Debería ser suficiente usar newValue allí, ese reloj solo se llama si algo ha cambiado. Si es un objeto, se llamará al instante.
Mathew Berg
Buen consejo, gracias! Cambiaré la ifdeclaración.
Evil Closet Monkey

Respuestas:

218

Debe habilitar la comprobación sucia de objetos profundos. Por defecto, angular solo verifica la referencia de la variable de nivel superior que observa.

App.directive('d3Visualization', function() {
    return {
        restrict: 'E',
        scope: {
            val: '='
        },
        link: function(scope, element, attrs) {
            scope.$watch('val', function(newValue, oldValue) {
                if (newValue)
                    console.log("I see a data change!");
            }, true);
        }
    }
});

ver Alcance . El tercer parámetro de la función $ watch permite una verificación profunda y sucia si está configurado en verdadero.

Tenga en cuenta que la verificación sucia profunda es costosa . Entonces, si solo necesita ver la matriz de niños en lugar de la datavariable completa , mire la variable directamente.

scope.$watch('val.children', function(newValue, oldValue) {}, true);

la versión 1.2.x introdujo $ watchCollection

Shallow observa las propiedades de un objeto y dispara cada vez que alguna de las propiedades cambia (para las matrices, esto implica observar los elementos de la matriz; para los mapas de objetos, esto implica observar las propiedades)

scope.$watchCollection('val.children', function(newValue, oldValue) {});
Liviu T.
fuente
1
Trabajando genial ¡Gracias!
Evil Closet Monkey
1
Encantado de ayudar. Siga experimentando con la función $ watch ya que las expresiones que puede ver son bastante variadas.
Liviu T.
1
Gracias por señalar cómo verificar solo los arrays profundos;)
veritas
Es una pena que no pueda votar más de una vez ... Te daría una recompensa, por las 2 horas que perdí buscando eso.
Alex Arvanitidis
mejor respuesta por ahí.
chovy
2

Porque si desea activar sus datos con profundidad, debe pasar el 3er argumento truede su oyente. Por defecto, es falsey significa que su función se activará, solo cuando su variable cambie, no su campo .

Hazarapet Tunanyan
fuente
1

Mi versión para una directiva que usa jqplot para trazar los datos una vez que esté disponible:

    app.directive('lineChart', function() {
        $.jqplot.config.enablePlugins = true;

        return function(scope, element, attrs) {
            scope.$watch(attrs.lineChart, function(newValue, oldValue) {
                if (newValue) {
                    // alert(scope.$eval(attrs.lineChart));
                    var plot = $.jqplot(element[0].id, scope.$eval(attrs.lineChart), scope.$eval(attrs.options));
                }
            });
        }
});
rob2universe
fuente