Comment regarder en profondeur un tableau dans angularjs?

320

Il y a un tableau d'objets dans ma portée, je veux regarder toutes les valeurs de chaque objet.

Voici mon code:

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

Mais quand je modifie les valeurs, par exemple je passe TITLEà TITLE2, le alert('columns changed')jamais sauté.

Comment observer en profondeur les objets à l'intérieur d'un tableau?

Il y a une démo en direct: http://jsfiddle.net/SYx9b/

Freewind
la source

Réponses:

529

Vous pouvez définir le 3e argument de $watchsur true:

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

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

Depuis Angular 1.1.x, vous pouvez également utiliser $ watchCollection pour regarder une montre superficielle (juste le "premier niveau") de la collection.

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

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

Piran
la source
2
Pourquoi utilisez-vous angular.equalslorsque le troisième argument prend une valeur booléenne ?
JayQuerie.com
Merci Trevor, lecture erronée des documents. J'ai mis à jour le code ci-dessus et mis à jour mon code pour qu'il corresponde.
Piran
36
dans 1.1.x vous avez maintenant$watchCollection
Jonathan Rowny
39
$watchCollectionne regardera que le "premier niveau" d'un tableau ou d'un objet, si je comprends bien. La réponse ci-dessus est correcte si vous devez regarder plus profondément que cela. bennadel.com/blog/…
Blazemonger
1
Excellente réponse ... vous aurait donné plus d'un vote positif si je pouvais ... :) merci
Jony-Y
50

La plongée en profondeur d'un objet dans votre montre a des conséquences sur les performances. Parfois (par exemple, lorsque les modifications ne sont que des push et des pops), vous souhaiterez peut-être $ regarder une valeur facilement calculable, telle que array.length.

wizardwerdna
la source
1
Cela devrait avoir plus de votes. Une observation approfondie coûte cher. Je comprends qu'OP recherchait une surveillance approfondie, mais les gens pourraient venir ici juste pour savoir si la baie elle-même a changé. Dans ce cas, regarder la longueur est beaucoup plus rapide.
Scott Silvi
42
Cela devrait être un commentaire, pas une réponse.
Blazemonger du
1
Les problèmes de performances seraient minimisés en utilisant $ watchCollection, comme mentionné par le commentaire @Blazemonger ( stackoverflow.com/questions/14712089#comment32440226_14713978 ).
Sean the Bean
Un bon conseil. Étant donné que ce commentaire n'est pas une réponse, à mon avis, il serait préférable d'élaborer la suggestion de remplir les critères d'acceptation d'une réponse. Je pense qu'avec cette approche, une solution élégante à la question peut être trouvée.
Amy Pellegrini
43

Si vous ne regardez qu'un seul tableau, vous pouvez simplement utiliser ce morceau de code:

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

exemple

Mais cela ne fonctionnera pas avec plusieurs tableaux:

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

exemple

Pour gérer cette situation, je convertis généralement les multiples tableaux que je veux regarder en JSON:

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

exemple

Comme l'a souligné @jssebastian dans les commentaires, il JSON.stringifypeut être préférable angular.toJsoncar il peut gérer les membres commençant par «$» et d'autres cas possibles également.

JayQuerie.com
la source
Je trouve aussi qu'il y a un 3ème paramètre $watch, est-il capable de faire de même? Pass true as a third argument to watch an object's properties too.Voir: cheatography.com/proloser/cheat-sheets/angularjs
Freewind
@Freewind S'il y a un cas où vous devrez regarder plusieurs tableaux, il échouera comme on le voit ici . Mais oui, cela fonctionnera aussi et offre les mêmes fonctionnalités que l'utilisation angular.toJsonsur une seule baie.
JayQuerie.com
2
Gardez juste à l'esprit que angular.toJson ne semble pas inclure les membres commençant par '$': angular.toJson ({"$ hello": "world"}) est juste "{}". J'ai utilisé JSON.stringify () comme alternative
jssebastian
@ jssebastian Merci - J'ai mis à jour la réponse pour inclure ces informations.
JayQuerie.com
1
pouvons-nous savoir quelle propriété a changé?
Ashwin du
21

Il convient de noter que dans Angular 1.1.x et supérieur, vous pouvez désormais utiliser $ watchCollection plutôt que $ watch. Bien que $ watchCollection semble créer des montres peu profondes, il ne fonctionnera donc pas avec des tableaux d'objets comme vous vous y attendez. Il peut détecter les ajouts et les suppressions au tableau, mais pas les propriétés des objets à l'intérieur des tableaux.

Jonathan Rowny
la source
5
Cependant, $ watchCollection ne regarde que peu profond. Donc, si je comprends bien, changer les propriétés des éléments du tableau (comme dans la question) ne déclencherait pas l'événement.
Tarnschaf
3
Cela devrait être un commentaire, pas une réponse.
Blazemonger du
18

Voici une comparaison des 3 façons dont vous pouvez regarder une variable de portée avec des exemples:

$ watch () est déclenché par:

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

$ watchCollection () est déclenché par tout ce qui précède ET:

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

$ watch (..., true) est déclenché par TOUT ci-dessus ET:

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

ENCORE UNE CHOSE...

$ watch () est le seul qui se déclenche lorsqu'un tableau est remplacé par un autre tableau même si cet autre tableau a le même contenu exact.

Par exemple, où $watch()tirerait et $watchCollection()ne serait pas:

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

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

$scope.myArray = newArray;

Vous trouverez ci-dessous un lien vers un exemple JSFiddle qui utilise toutes les différentes combinaisons de montres et génère des messages de journal pour indiquer les "montres" qui ont été déclenchées:

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

Luis Perez
la source
Exactement, surtout pour noter la `` ONE MORE THING ''
Andy Ma
12

$ watchCollection accomplit ce que vous voulez faire. Ci-dessous, un exemple copié à partir du site Web angularjs http://docs.angularjs.org/api/ng/type/$rootScope.Scope Bien que cela soit pratique, les performances doivent être prises en considération, surtout lorsque vous regardez une grande collection.

  $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
la source
14
OP a spécifié un tableau d'objets. Votre exemple fonctionne avec un tableau de chaînes, mais $ watchCollection ne fonctionne pas avec un tableau d'objets.
KevinL
4

Cette solution a très bien fonctionné pour moi, je le fais dans une directive:

portée. $ watch (attrs.testWatch, function () {.....}, true);

le vrai fonctionne assez bien et réagit pour tous les changements (ajouter, supprimer ou modifier un champ).

Voici un plongeur de travail pour jouer avec.

Regarder en profondeur un tableau dans AngularJS

J'espère que cela peut vous être utile. Si vous avez des questions, n'hésitez pas à demander, je vais essayer de vous aider :)

EPotignano
la source
4

Dans mon cas, j'avais besoin de regarder un service, qui contient un objet adresse également surveillé par plusieurs autres contrôleurs. J'étais coincé dans une boucle jusqu'à ce que j'ajoute le «vrai» paramètre, qui semble être la clé du succès lors de l'observation d'objets.

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

La définition du objectEqualityparamètre (troisième paramètre) de la $watchfonction est certainement la bonne façon de regarder TOUTES les propriétés du tableau.

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

Piran répond assez bien à cette question et mentionne $watchCollectionégalement.

Plus de détails

La raison pour laquelle je réponds à une question déjà répondue est parce que je veux souligner que la réponse de wizardwerdna n'est pas bonne et ne doit pas être utilisée.

Le problème est que les résumés ne se font pas immédiatement. Ils doivent attendre la fin du bloc de code actuel avant de s'exécuter. Ainsi, regarder lengthun tableau peut manquer certains changements importants qui se $watchCollectionproduiront.

Supposons cette configuration:

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

À première vue, il peut sembler que ceux-ci se déclencheraient en même temps, comme dans ce cas:

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

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

Cela fonctionne assez bien, mais considérez ceci:

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

// Console output
// testArray changed

Notez que la longueur résultante était la même, même si le tableau a un nouvel élément et a perdu un élément, de sorte que watch as the $watchconcern, lengthn'a pas changé. $watchCollectioncependant.

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

// Console output
// testArray change

Le même résultat se produit avec un push and pop dans le même bloc.

Conclusion

Pour surveiller chaque propriété du tableau, utilisez un $watchsur le tableau lui-même avec le troisième paramètre (objectEquality) inclus et défini sur true. Oui, c'est cher mais parfois nécessaire.

Pour regarder quand un objet entre / sort du tableau, utilisez a $watchCollection.

N'utilisez PAS a $watchsur la lengthpropriété du tableau. Il n'y a presque aucune bonne raison de penser à le faire.

Patrick John Haskins
la source
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
la source