Como observar profundamente uma matriz em angularjs?

320

Há uma matriz de objetos no meu escopo, quero observar todos os valores de cada objeto.

Este é o meu código:

function TodoCtrl($scope) {
  $scope.columns = [
      { field:'title', displayName: 'TITLE'},
      { field: 'content', displayName: 'CONTENT' }
  ];
   $scope.$watch('columns', function(newVal) {
       alert('columns changed');
   });
}

Mas quando eu modifico os valores, por exemplo, mudo TITLEpara TITLE2, o alert('columns changed')nunca aparece.

Como observar profundamente os objetos dentro de uma matriz?

Há uma demonstração ao vivo: http://jsfiddle.net/SYx9b/

Vento livre
fonte

Respostas:

529

Você pode definir o terceiro argumento de $watchpara true:

$scope.$watch('data', function (newVal, oldVal) { /*...*/ }, true);

Consulte https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch

Desde o Angular 1.1.x, você também pode usar o $ watchCollection para assistir à exibição rasa (apenas o "primeiro nível") da coleção.

$scope.$watchCollection('data', function (newVal, oldVal) { /*...*/ });

Consulte https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watchCollection

Piran
fonte
2
Por que você está usando angular.equalsquando o terceiro argumento recebe um valor booleano ?
JayQuerie.com
Obrigado Trevor, leitura incorreta dos documentos. Atualizei o código acima e atualizei meu código para corresponder.
5113 Piran
36
em 1.1.x agora você tem$watchCollection
Jonathan Rowny
39
$watchCollectionassistirá apenas o "primeiro nível" de uma matriz ou objeto, como eu o entendo. A resposta acima está correta se você precisar prestar atenção mais profunda do que isso. bennadel.com/blog/…
Blazemonger
1
Grande resposta ... teria lhe dado mais de um up vote se eu pudesse ... :) graças
Jony-Y
50

Há consequências de desempenho para mergulhar profundamente um objeto no seu $ watch. Às vezes (por exemplo, quando as alterações são apenas push e pop), você pode querer assistir a um valor facilmente calculado, como array.length.

wizardwerdna
fonte
1
Isso deve ter mais votos. A observação profunda é cara. Entendo que o OP estava procurando uma observação profunda, mas as pessoas podem vir aqui apenas querendo saber se a matriz em si mudou. Observar o comprimento é muito mais rápido, nesse caso.
23813 Scott Silvi #
42
Isso deve ser um comentário, não uma resposta.
Blazemonger
1
Os problemas de desempenho seriam minimizados usando $ watchCollection, conforme mencionado no comentário @Blazemonger ( stackoverflow.com/questions/14712089#comment32440226_14713978 ).
Sean the Bean
Conselhos legais. Considerando que este comentário não é uma resposta, na minha opinião, seria melhor elaborar a sugestão para atender aos critérios de aceitação de uma resposta. Eu acho que com essa abordagem uma solução elegante para a questão pode ser alcançada.
Amy Pellegrini
43

Se você estiver assistindo apenas uma matriz, pode simplesmente usar este pedaço de código:

$scope.$watch('columns', function() {
  // some value in the array has changed 
}, true); // watching properties

exemplo

Mas isso não funcionará com várias matrizes:

$scope.$watch('columns + ANOTHER_ARRAY', function() {
  // will never be called when things change in columns or ANOTHER_ARRAY
}, true);

exemplo

Para lidar com essa situação, costumo converter as várias matrizes que desejo assistir em JSON:

$scope.$watch(function() { 
  return angular.toJson([$scope.columns, $scope.ANOTHER_ARRAY, ... ]); 
},
function() {
  // some value in some array has changed
}

exemplo

Como o @jssebastian apontou nos comentários, JSON.stringifypode ser preferível, angular.toJsonpois ele pode lidar com membros que começam com '$' e também com outros casos possíveis.

JayQuerie.com
fonte
Eu também acho que existe um terceiro parâmetro para $watch, ele é capaz de fazer o mesmo? Pass true as a third argument to watch an object's properties too.Veja: cheaptography.com/proloser/cheat-sheets/angularjs
Freewind
@Freewind Se houver um caso em que você precisará assistir a várias matrizes, ele falhará como visto aqui . Mas sim, isso também funcionará e fornece a mesma funcionalidade que o uso angular.toJsonem uma única matriz.
JayQuerie.com
2
Apenas observe que angular.toJson parece não incluir membros que começam com '$': angular.toJson ({"$ hello": "world"}) é apenas "{}". I utilizado JSON.stringify () como uma alternativa
jssebastian
@ jssebastian Obrigado - atualizei a resposta para incluir essas informações.
JayQuerie.com
1
podemos saber qual propriedade mudou?
Ashwin
21

Vale a pena notar que no Angular 1.1.xe acima, agora você pode usar $ watchCollection em vez de $ watch. Embora o $ watchCollection pareça criar relógios rasos, ele não funcionará com matrizes de objetos como você espera. Ele pode detectar adições e exclusões na matriz, mas não as propriedades dos objetos dentro das matrizes.

Jonathan Rowny
fonte
5
$ watchCollection, no entanto, apenas relógios rasos. Pelo que entendi, alterar as propriedades dos itens da matriz (como na pergunta) não acionaria o evento.
Tarnschaf 27/10/2013
3
Isso deve ser um comentário, não uma resposta.
Blazemonger
18

Aqui está uma comparação das três maneiras pelas quais você pode assistir uma variável de escopo com exemplos:

$ watch () é acionado por:

$scope.myArray = [];
$scope.myArray = null;
$scope.myArray = someOtherArray;

$ watchCollection () é acionado por tudo acima AND:

$scope.myArray.push({}); // add element
$scope.myArray.splice(0, 1); // remove element
$scope.myArray[0] = {}; // assign index to different value

$ watch (..., true) é acionado por TUDO acima E:

$scope.myArray[0].someProperty = "someValue";

SÓ MAIS UMA COISA...

$ watch () é o único acionado quando uma matriz é substituída por outra matriz, mesmo que essa outra matriz tenha o mesmo conteúdo exato.

Por exemplo, onde $watch()seria acionado e $watchCollection()não:

$scope.myArray = ["Apples", "Bananas", "Orange" ];

var newArray = [];
newArray.push("Apples");
newArray.push("Bananas");
newArray.push("Orange");

$scope.myArray = newArray;

Abaixo está um link para um exemplo do JSFiddle que usa todas as diferentes combinações de relógios e gera mensagens de log para indicar quais "relógios" foram acionados:

http://jsfiddle.net/luisperezphd/2zj9k872/

Luis Perez
fonte
Exatamente, especialmente para observar o 'MAIS UMA COISA'
Andy Ma
12

$ watchCollection realiza o que você deseja fazer. Abaixo está um exemplo copiado do site da angularjs http://docs.angularjs.org/api/ng/type/$rootScope.Scope Embora seja conveniente, o desempenho precisa ser levado em consideração, especialmente quando você assiste a uma grande coleção.

  $scope.names = ['igor', 'matias', 'misko', 'james'];
  $scope.dataCount = 4;

  $scope.$watchCollection('names', function(newNames, oldNames) {
     $scope.dataCount = newNames.length;
  });

  expect($scope.dataCount).toEqual(4);
  $scope.$digest();

  //still at 4 ... no changes
  expect($scope.dataCount).toEqual(4);

  $scope.names.pop();
  $scope.$digest();

  //now there's been a change
  expect($scope.dataCount).toEqual(3);
Jin
fonte
14
OP especificou uma matriz de objetos. Seu exemplo funciona com uma matriz de seqüências de caracteres, mas $ watchCollection não funciona com uma matriz de objetos.
KevinL
4

Esta solução funcionou muito bem para mim, estou fazendo isso em uma diretiva:

escopo. $ watch (attrs.testWatch, function () {.....}, true);

o true funciona muito bem e reage a todos os objetos (adicionar, excluir ou modificar um campo).

Aqui está um plunker de trabalho para brincar com ele.

Assistindo profundamente uma matriz no AngularJS

Espero que isso possa ser útil para você. Se você tiver alguma dúvida, sinta-se à vontade para perguntar, tentarei ajudar :)

EPotignano
fonte
4

No meu caso, eu precisava assistir a um serviço, que contém um objeto de endereço também assistido por vários outros controladores. Fiquei preso em um loop até adicionar o parâmetro 'true', que parece ser a chave do sucesso ao assistir objetos.

$scope.$watch(function() {
    return LocationService.getAddress();
}, function(address) {
    //handle address object
}, true);
RevNoah
fonte
1

Definir o objectEqualityparâmetro (terceiro parâmetro) da $watchfunção é definitivamente a maneira correta de observar TODAS as propriedades da matriz.

$scope.$watch('columns', function(newVal) {
    alert('columns changed');
},true); // <- Right here

Piran responde isso bem o suficiente e menciona $watchCollectiontambém.

Mais detalhes

O motivo pelo qual estou respondendo uma pergunta já respondida é porque quero ressaltar que a resposta do wizardwerdna não é boa e não deve ser usada.

O problema é que os resumos não acontecem imediatamente. Eles precisam esperar até que o bloco de código atual seja concluído antes de executar. Portanto, assistir o lengthde uma matriz pode realmente perder algumas alterações importantes que $watchCollectionserão detectadas.

Suponha esta configuração:

$scope.testArray = [
    {val:1},
    {val:2}
];

$scope.$watch('testArray.length', function(newLength, oldLength) {
    console.log('length changed: ', oldLength, ' -> ', newLength);
});

$scope.$watchCollection('testArray', function(newArray) {
    console.log('testArray changed');
});

À primeira vista, pode parecer que estes disparariam ao mesmo tempo, como neste caso:

function pushToArray() {
    $scope.testArray.push({val:3});
}
pushToArray();

// Console output
// length changed: 2 -> 3
// testArray changed

Isso funciona bem o suficiente, mas considere isso:

function spliceArray() {
    // Starting at index 1, remove 1 item, then push {val: 3}.
    $testArray.splice(1, 1, {val: 3});
}
spliceArray();

// Console output
// testArray changed

Observe que o comprimento resultante foi o mesmo, mesmo que a matriz tenha um novo elemento e tenha perdido um elemento, portanto, observe o que $watchdiz respeito, lengthnão mudou. $watchCollectionpegou nele, no entanto.

function pushPopArray() {
    $testArray.push({val: 3});
    $testArray.pop();
}
pushPopArray();

// Console output
// testArray change

O mesmo resultado acontece com um push e pop no mesmo bloco.

Conclusão

Para assistir a todas as propriedades da matriz, use $watcha própria matriz com o terceiro parâmetro (objectEquality) incluído e definido como true. Sim, isso é caro, mas às vezes necessário.

Para observar quando o objeto entra / sai da matriz, use a $watchCollection.

NÃO use a $watchna lengthpropriedade da matriz. Quase não há uma boa razão para pensar nisso.

Patrick John Haskins
fonte
0

$scope.changePass = function(data){
    
    if(data.txtNewConfirmPassword !== data.txtNewPassword){
        $scope.confirmStatus = true;
    }else{
        $scope.confirmStatus = false;
    }
};
  <form class="list" name="myForm">
      <label class="item item-input">        
        <input type="password" placeholder="ใส่รหัสผ่านปัจจุบัน" ng-model="data.txtCurrentPassword" maxlength="5" required>
      </label>
      <label class="item item-input">
        <input type="password" placeholder="ใส่รหัสผ่านใหม่" ng-model="data.txtNewPassword" maxlength="5" ng-minlength="5" name="checknawPassword" ng-change="changePass(data)" required>
      </label>
      <label class="item item-input">
        <input type="password" placeholder="ใส่รหัสผ่านใหม่ให้ตรงกัน" ng-model="data.txtNewConfirmPassword" maxlength="5" ng-minlength="5" name="checkConfirmPassword" ng-change="changePass(data)" required>
      </label>      
       <div class="spacer" style="width: 300px; height: 5px;"></div> 
      <span style="color:red" ng-show="myForm.checknawPassword.$error.minlength || myForm.checkConfirmPassword.$error.minlength">รหัสผ่านต้องมีจำนวน 5 หลัก</span><br>
      <span ng-show="confirmStatus" style="color:red">รหัสผ่านใหม่ไม่ตรงกัน</span>
      <br>
      <button class="button button-positive  button-block" ng-click="saveChangePass(data)" ng-disabled="myForm.$invalid || confirmStatus">เปลี่ยน</button>
    </form>

Fluke Klumame
fonte