Regarder plusieurs attributs $ scope

442

Existe-t-il un moyen de s'abonner à des événements sur plusieurs objets en utilisant $watch

Par exemple

$scope.$watch('item1, item2', function () { });
Greg
la source

Réponses:

586

À partir d'AngularJS 1.3, il existe une nouvelle méthode appelée $watchGrouppour observer un ensemble d'expressions.

$scope.foo = 'foo';
$scope.bar = 'bar';

$scope.$watchGroup(['foo', 'bar'], function(newValues, oldValues, scope) {
  // newValues array contains the current values of the watch expressions
  // with the indexes matching those of the watchExpression array
  // i.e.
  // newValues[0] -> $scope.foo 
  // and 
  // newValues[1] -> $scope.bar 
});
Paolo Moretti
la source
7
quelle est la différence avec $ watchCollection ('[a, b]') ??
Kamil Tomšík
28
La différence est que vous pouvez utiliser un tableau approprié au lieu d'une chaîne qui ressemble à un tableau
Paolo Moretti
2
J'ai eu un problème similaire, mais en utilisant le modèle controllerAs. Dans mon cas, le correctif consistait à ajouter aux variables le nom du contrôleur:$scope.$watchGroup(['mycustomctrl.foo', 'mycustomctrl.bar'],...
morilles
1
Cela ne semble pas fonctionner pour moi sur Angular 1.6. Si je mets un journal de console sur la fonction, je peux voir qu'il ne s'exécute qu'une seule fois avec newValueset oldValuesas undefined, tout en regardant individuellement les propriétés fonctionner comme d'habitude.
Alejandro García Iglesias
290

À partir d'AngularJS 1.1.4, vous pouvez utiliser $watchCollection:

$scope.$watchCollection('[item1, item2]', function(newValues, oldValues){
    // do stuff here
    // newValues and oldValues contain the new and respectively old value
    // of the observed collection array
});

Exemple Plunker ici

Documentation ici

Răzvan Flavius ​​Panda
la source
109
Notez que le premier paramètre n'est pas un tableau. C'est une chaîne qui ressemble à un tableau.
MK Safi
1
Merci pour cela. si cela fonctionne pour moi, je vous donnerai mille pièces d'or - virtuelles, bien sûr :)
AdityaSaxena
5
Pour être clair (il m'a fallu quelques minutes pour réaliser), il $watchCollectionest prévu de remettre un objet et de surveiller les modifications de l'une des propriétés de l'objet, alors qu'il $watchGroupest destiné à recevoir un tableau de propriétés individuelles pour surveiller les modifications. Légèrement différent pour s'attaquer à des problèmes similaires mais différents. Phew!
Mattygabe
1
D'une manière ou d'une autre, newValues ​​et oldValues ​​sont toujours égales lorsque vous utilisez cette méthode: plnkr.co/edit/SwwdpQcNf78pQ80hhXMr
mostruash
Merci. Cela a résolu mon problème. Y a-t-il des problèmes / problèmes de performances liés à l'utilisation de $ watch?
Syed Nasir Abbas
118

$watch le premier paramètre peut également être une fonction.

$scope.$watch(function watchBothItems() {
  return itemsCombinedValue();
}, function whenItemsChange() {
  //stuff
});

Si vos deux valeurs combinées sont simples, le premier paramètre n'est normalement qu'une expression angulaire. Par exemple, firstName et lastName:

$scope.$watch('firstName + lastName', function() {
  //stuff
});
Andrew Joslin
la source
8
Cela ressemble à un cas d'utilisation qui demande par défaut d'être inclus dans le cadre. Même une propriété calculée aussi simple que fullName = firstName + " " + lastNamenécessite de passer une fonction comme premier argument (plutôt qu'une simple expression comme 'firstName, lastName'). Angular est SI puissant, mais il y a certains domaines où il peut être énormément plus convivial pour les développeurs d'une manière qui ne semble pas compromettre les performances ou les principes de conception.
XML
4
Eh bien $ watch prend juste une expression angulaire, qui est évaluée sur la portée à laquelle elle est appelée. Vous pourriez donc faire $scope.$watch('firstName + lastName', function() {...}). Vous pouvez aussi simplement faire deux montres: function nameChanged() {}; $scope.$watch('firstName', nameChanged); $scope.$watch('lastName', nameChanged);. J'ai ajouté le cas simple à la réponse.
Andrew Joslin
Le plus dans 'firstName + lastName' est la concaténation de chaînes. Donc, angulaire vérifie simplement si le résultat de la concaténation est différent maintenant.
mb21
16
La concaténation de chaînes nécessite un peu de soin - si vous changez "paran orman" en "para norman", vous ne déclencherez pas de réponse; mieux de mettre un séparateur comme "\ t | \ t" entre le premier et le deuxième mandat, ou simplement s'en tenir à JSON qui est définitif
Dave Edelhart
bien que amberjs suce, mais il a cette fonctionnalité intégrée! entendre que les gars anguleux. Je suppose que faire des montres est le moyen le plus rationnel.
Aladdin Mhemed
70

Voici une solution très similaire à votre pseudo-code d'origine qui fonctionne réellement:

$scope.$watch('[item1, item2] | json', function () { });

EDIT: D'accord, je pense que c'est encore mieux:

$scope.$watch('[item1, item2]', function () { }, true);

Fondamentalement, nous sautons l'étape json, qui semblait stupide au début, mais cela ne fonctionnait pas sans elle. Leur clé est le 3e paramètre souvent omis qui active l'égalité d'objet par opposition à l'égalité de référence. Ensuite, les comparaisons entre nos objets de tableau créés fonctionnent correctement.

Karen Zilles
la source
J'avais une question similaire. Et c'est la bonne réponse pour moi.
Calvin Cheng
Les deux ont une légère surcharge de performances si les éléments de l'expression de tableau ne sont pas des types simples.
null
C'est vrai .. il fait une comparaison complète des objets composants. Ce qui peut ou non être ce que vous voulez. Voir la réponse actuellement approuvée pour une version utilisant un angulaire moderne qui ne fait pas de comparaison approfondie.
Karen Zilles
Pour une raison quelconque, lorsque j'essayais d'utiliser $ watchCollection sur un tableau, j'ai eu cette erreur TypeError: Object #<Object> has no method '$watchCollection'mais cette solution m'aide à résoudre mon problème!
abottoni
Cool, vous pouvez même regarder des valeurs dans des objets:$scope.$watch('[chaptercontent.Id, anchorid]', function (newValues, oldValues) { ... }, true);
Serge van den Oever
15

Vous pouvez utiliser des fonctions dans $ watchGroup pour sélectionner des champs d'un objet dans la portée.

        $scope.$watchGroup(
        [function () { return _this.$scope.ViewModel.Monitor1Scale; },   
         function () { return _this.$scope.ViewModel.Monitor2Scale; }],  
         function (newVal, oldVal, scope) 
         {
             if (newVal != oldVal) {
                 _this.updateMonitorScales();
             }
         });
Yang Zhang
la source
1
Pourquoi utilisez-vous _this? La portée n'est-elle pas portée dans les fonctions sans la définir explicitement?
sg.cc
1
J'utilise tapuscrit, le code présenté est le résultat compilé.
Yang Zhang
12

Pourquoi ne pas simplement l'envelopper dans un forEach?

angular.forEach(['a', 'b', 'c'], function (key) {
  scope.$watch(key, function (v) {
    changed();
  });
});

C'est à peu près la même chose que de fournir une fonction pour la valeur combinée, sans avoir à se soucier de la composition de la valeur .

Erik Aigner
la source
Dans ce cas, log () serait exécuté plusieurs fois, non? Je pense qu'en cas de fonctions $ watch imbriquées, ce ne serait pas le cas. Et cela ne devrait pas être la solution pour regarder plusieurs attributs à la fois.
John Doe
Non, le journal n'est exécuté qu'une fois par modification par propriété surveillée. Pourquoi serait-il invoqué plusieurs fois?
Erik Aigner
Bon, je veux dire que ça ne marcherait pas bien si nous voulions tous les regarder simultanément.
John Doe
1
Bien sûr, pourquoi pas? Je le fais de cette façon dans un de mes projets. changed()est invoqué chaque fois que quelque chose change dans l'une des propriétés. C'est exactement le même comportement que si une fonction pour la valeur combinée était fournie.
Erik Aigner
1
C'est légèrement différent en ce que si plusieurs des éléments du tableau changent en même temps, $ watch ne déclenchera le gestionnaire qu'une seule fois au lieu d'une fois par élément modifié.
bingles
11

Une solution légèrement plus sûre pour combiner des valeurs pourrait être d'utiliser la fonction suivante comme $watchfonction:

function() { return angular.toJson([item1, item2]) }

ou

$scope.$watch(
  function() {
    return angular.toJson([item1, item2]);
  },
  function() {
    // Stuff to do after either value changes
  });
guyk
la source
5

Le paramètre $ watch first peut être une expression ou une fonction angulaire . Voir la documentation sur $ scope. $ Watch . Il contient de nombreuses informations utiles sur le fonctionnement de la méthode $ watch: lorsque watchExpression est appelée, comment angulaire compare les résultats, etc.

Artem Andreev
la source
4

que diriez-vous:

scope.$watch(function() { 
   return { 
      a: thing-one, 
      b: thing-two, 
      c: red-fish, 
      d: blue-fish 
   }; 
}, listener...);
wacamo
la source
2
Sans le troisième trueparamètre to scope.$watch(comme dans votre exemple), listener()se déclencherait à chaque fois, car le hachage anonyme a une référence différente à chaque fois.
Peter V. Mørch
J'ai trouvé cette solution adaptée à ma situation. Pour moi, il était plus comme: return { a: functionA(), b: functionB(), ... }. Je vérifie ensuite les objets retournés des nouvelles et anciennes valeurs les uns par rapport aux autres.
RevNoah
4
$scope.$watch('age + name', function () {
  //called when name or age changed
});

Ici, la fonction sera appelée lorsque l'âge et la valeur du nom seront modifiés.

Akash Shinde
la source
2
Assurez-vous également dans ce cas de ne pas utiliser les arguments newValue et oldValue qui passent angulaires dans le rappel car ils seront la concaténation résultante et pas seulement le champ qui a été modifié
Akash Shinde
1

Angular introduit $watchGroupdans la version 1.3 à l'aide duquel nous pouvons regarder plusieurs variables, avec un seul $watchGroupbloc $watchGroupprend le tableau comme premier paramètre dans lequel nous pouvons inclure toutes nos variables à regarder.

$scope.$watchGroup(['var1','var2'],function(newVals,oldVals){
   console.log("new value of var1 = " newVals[0]);
   console.log("new value of var2 = " newVals[1]);
   console.log("old value of var1 = " oldVals[0]);
   console.log("old value of var2 = " oldVals[1]);
});
Vinit Solanki
la source