$ observando alterações de dados em uma diretiva Angular

132

Como posso disparar uma $watchvariável em uma diretiva Angular ao manipular os dados internos (por exemplo, inserir ou remover dados), mas não atribuir um novo objeto a essa variável?

Atualmente, tenho um conjunto de dados simples sendo carregado de um arquivo JSON. Meu controlador Angular faz isso, bem como define algumas funções:

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() {

    };
});

Eu tenho uma <button>que é chamada corretamente de $scope.addfunção e o novo objeto é inserido corretamente no $scope.dataconjunto. Uma tabela que eu configurei é atualizada sempre que clico no botão "adicionar".

<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>

No entanto, uma diretiva que eu configurei para assistir $scope.datanão está sendo acionada quando tudo isso acontece.

Eu defino minha tag em HTML:

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

Que está associado à seguinte diretiva (aparada para sanidade das perguntas):

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!");
            });
        }
    }
});

Recebo a "I see a data change!"mensagem logo no início, mas nunca depois de clicar no botão "adicionar".

Como posso acionar o $watchevento quando estou apenas adicionando / removendo objetos do dataobjeto, sem obter um conjunto de dados totalmente novo para atribuir ao dataobjeto?

Evil Closet Monkey
fonte
5
Apenas uma dica rápida se newValue for igual a 0, false "" "ou qualquer outra coisa assim:" Eu vejo uma alteração de dados! " não vai atirar. Por exemplo, o valor original era true, o newValue é false. Deve ser suficiente usar newValue, o relógio só será chamado se algo mudar. Se for um objeto, será chamado instantaneamente.
Mathew Berg
Boa dica, obrigado! Vou mudar a ifdeclaração.
Evil Closet Monkey

Respostas:

218

Você precisa habilitar a verificação suja de objetos profundos. Por padrão, angular verifica apenas a referência da variável de nível superior que você assiste.

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);
        }
    }
});

veja Escopo . O terceiro parâmetro da função $ watch permite uma verificação profunda e suja, se estiver definida como true.

Observe que a verificação suja profunda é cara . Portanto, se você precisar apenas observar a matriz filho em vez de toda a datavariável, observe a variável diretamente.

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

a versão 1.2.x introduziu $ watchCollection

Shallow observa as propriedades de um objeto e dispara sempre que alguma das propriedades muda (para matrizes, isso implica observar os itens da matriz; para mapas de objetos, isso implica observar as propriedades)

scope.$watchCollection('val.children', function(newValue, oldValue) {});
Liviu T.
fonte
1
Trabalhando muito bem. Obrigado!
Evil Closet Monkey
1
Feliz em ajudar. Continue experimentando a função $ watch, pois as expressões que ela pode assistir são bastante variadas.
Liviu T.
1
Obrigado por notar como verificar matrizes única profundas;)
veritas
É uma pena que eu não possa votar mais de uma vez .. Eu te daria uma recompensa, pelas 2h que perdi procurando por isso.
Alex Arvanitidis
melhor resposta lá fora.
chovy 19/07/2015
2

Como se você deseja acionar seus dados com profundidade, é necessário passar o terceiro argumento truedo seu ouvinte. Por padrão, é falsee me parece que sua função será acionada, apenas quando sua variável mudar, não é o campo .

Hazarapet Tunanyan
fonte
1

Minha versão de uma diretiva que usa jqplot para plotar os dados assim que estiverem disponíveis:

    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
fonte