Beobachten Sie mehrere $ scope-Attribute

441

Gibt es eine Möglichkeit, Ereignisse für mehrere Objekte mit zu abonnieren? $watch

Z.B

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

Antworten:

585

Ab AngularJS 1.3 gibt es eine neue Methode $watchGroupzum Beobachten einer Reihe von Ausdrücken.

$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
quelle
7
Was ist der Unterschied zu $ ​​watchCollection ('[a, b]')?
Kamil Tomšík
28
Der Unterschied ist, dass Sie ein richtiges Array anstelle eines Strings verwenden können, der wie ein Array aussieht
Paolo Moretti
2
Ich hatte ein ähnliches Problem, verwendete aber das ControllerAs-Muster. In meinem Fall bestand der Fix darin, den Variablen den Namen des Controllers $scope.$watchGroup(['mycustomctrl.foo', 'mycustomctrl.bar'],...
voranzustellen
1
Bei Angular 1.6 scheint dies bei mir nicht zu funktionieren. Wenn ich der Funktion ein Konsolenprotokoll hinzufüge, kann ich sehen, dass sie nur einmal mit newValuesund oldValuesals ausgeführt wird undefined, während die Eigenschaften einzeln wie gewohnt funktionieren.
Alejandro García Iglesias
290

Ab AngularJS 1.1.4 können Sie Folgendes verwenden $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
});

Plunker Beispiel hier

Dokumentation hier

Răzvan Flavius ​​Panda
quelle
109
Beachten Sie, dass der erste Parameter kein Array ist. Es ist eine Zeichenfolge, die wie ein Array aussieht.
MK Safi
1
Danke dafür. Wenn es für mich funktioniert, gebe ich Ihnen tausend Goldmünzen - natürlich virtuelle :)
AdityaSaxena
5
Um klar zu sein (ich habe ein paar Minuten gebraucht, um es zu realisieren) $watchCollection, soll ein Objekt übergeben werden und eine Überwachung für Änderungen an den Eigenschaften des Objekts bereitstellen, während $watchGroupeine Reihe von einzelnen Eigenschaften übergeben werden soll, um auf Änderungen zu achten. Etwas anders, um ähnliche, aber unterschiedliche Probleme anzugehen. Puh!
Mattygabe
1
Irgendwie sind newValues ​​und oldValues ​​immer gleich, wenn Sie diese Methode verwenden: plnkr.co/edit/SwwdpQcNf78pQ80hhXMr
mostruash
Vielen Dank. Es hat mein Problem gelöst. Gibt es einen Performance-Hit / ein Problem bei der Verwendung von $ watch?
Syed Nasir Abbas
118

$watch Der erste Parameter kann auch eine Funktion sein.

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

Wenn Ihre beiden kombinierten Werte einfach sind, ist der erste Parameter normalerweise nur ein Winkelausdruck. Zum Beispiel Vorname und Nachname:

$scope.$watch('firstName + lastName', function() {
  //stuff
});
Andrew Joslin
quelle
8
Dies scheint ein Anwendungsfall zu sein, der standardmäßig nach einer Aufnahme in das Framework verlangt. Sogar eine so einfache berechnete Eigenschaft fullName = firstName + " " + lastNameerfordert die Übergabe einer Funktion als erstes Argument (anstelle eines einfachen Ausdrucks wie 'firstName, lastName'). Angular ist SO leistungsfähig, aber es gibt einige Bereiche, in denen es auf eine Weise, die die Leistung oder die Designprinzipien nicht beeinträchtigt (scheinbar), erheblich entwicklerfreundlicher sein kann.
XML
4
Nun, $ watch verwendet nur einen Winkelausdruck, der anhand des Bereichs bewertet wird, in dem er aufgerufen wird. Also könntest du es tun $scope.$watch('firstName + lastName', function() {...}). Sie könnten auch nur zwei Uhren machen : function nameChanged() {}; $scope.$watch('firstName', nameChanged); $scope.$watch('lastName', nameChanged);. Ich habe der Antwort den einfachen Fall hinzugefügt.
Andrew Joslin
Das Plus in 'Vorname + Nachname' ist die Verkettung von Zeichenfolgen. Angular prüft also nur, ob das Ergebnis der Verkettung jetzt anders ist.
mb21
16
Die Verkettung von Zeichenfolgen erfordert ein wenig Sorgfalt. Wenn Sie "paran orman" in "para norman" ändern, wird keine Antwort ausgelöst. Am besten setzen Sie einen Teiler wie "\ t | \ t" zwischen den ersten und zweiten Term oder bleiben Sie einfach bei JSON, was definitiv ist
Dave Edelhart
Obwohl Amberjs scheiße ist, hat es diese Funktion eingebaut! höre diese eckigen Jungs. Ich denke, Uhren zu machen ist der rationalste Weg.
Aladdin Mhemed
70

Hier ist eine Lösung, die Ihrem ursprünglichen Pseudocode sehr ähnlich ist und tatsächlich funktioniert:

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

EDIT: Okay, ich denke das ist noch besser:

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

Grundsätzlich überspringen wir den JSON-Schritt, der anfangs dumm schien, aber ohne ihn nicht funktioniert hat. Der Schlüssel ist der häufig ausgelassene dritte Parameter, der die Objektgleichheit im Gegensatz zur Referenzgleichheit aktiviert. Dann funktionieren die Vergleiche zwischen unseren erstellten Array-Objekten tatsächlich richtig.

Karen Zilles
quelle
Ich hatte eine ähnliche Frage. Und das ist die richtige Antwort für mich.
Calvin Cheng
Beide haben einen geringen Leistungsaufwand, wenn die Elemente im Array-Ausdruck keine einfachen Typen sind.
null
Das ist wahr. Es wird ein vollständiger Tiefenvergleich der Komponentenobjekte durchgeführt. Welches kann oder kann nicht sein, was Sie wollen. Sehen Sie sich die derzeit genehmigte Antwort für eine Version mit modernem Winkel an, die keinen vollständigen Tiefenvergleich durchführt.
Karen Zilles
Aus irgendeinem Grund, als ich versuchte, die $ watchCollection über ein Array zu verwenden, wurde dieser Fehler angezeigt, TypeError: Object #<Object> has no method '$watchCollection'aber diese Lösung hilft mir, mein Problem zu lösen!
Abottoni
Cool, Sie können sogar Werte innerhalb von Objekten beobachten:$scope.$watch('[chaptercontent.Id, anchorid]', function (newValues, oldValues) { ... }, true);
Serge van den Oever
15

Sie können Funktionen in $ watchGroup verwenden, um Felder eines Objekts im Bereich auszuwählen.

        $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
quelle
1
Warum benutzt du _this? Wird der Bereich nicht in die Funktionen übernommen, ohne ihn explizit zu definieren?
sg.cc
1
Ich benutze Typoskript, der präsentierte Code ist kompiliertes Ergebnis.
Yang Zhang
12

Warum nicht einfach einpacken forEach?

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

Es ist ungefähr der gleiche Aufwand wie das Bereitstellen einer Funktion für den kombinierten Wert, ohne sich um die Wertzusammensetzung kümmern zu müssen .

Erik Aigner
quelle
In diesem Fall würde log () mehrmals ausgeführt, oder? Ich denke, im Fall von verschachtelten $ watch-Funktionen würde dies nicht der Fall sein. Und es sollte nicht in der Lösung sein, mehrere Attribute gleichzeitig zu beobachten.
John Doe
Nein, das Protokoll wird nur einmal pro Änderung pro beobachteter Eigenschaft ausgeführt. Warum sollte es mehrmals aufgerufen werden?
Erik Aigner
Richtig, ich meine, es würde nicht gut funktionieren, wenn wir alle gleichzeitig sehen wollen.
John Doe
1
Sicher warum nicht? Ich mache das so in einem meiner Projekte. changed()wird immer dann aufgerufen, wenn sich in einer der Eigenschaften etwas ändert. Das ist genau das gleiche Verhalten, als ob eine Funktion für den kombinierten Wert bereitgestellt worden wäre.
Erik Aigner
1
Es ist insofern etwas anders, als wenn sich mehrere Elemente im Array gleichzeitig ändern, die $ watch den Handler nur einmal anstatt einmal pro geändertem Element auslöst.
Bingles
11

Eine etwas sicherere Lösung zum Kombinieren von Werten könnte darin bestehen, Folgendes als $watchFunktion zu verwenden:

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

oder

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

Der Parameter $ watch first kann ein Winkelausdruck oder eine Funktion sein. Siehe Dokumentation zu $ scope. $ Watch . Es enthält viele nützliche Informationen zur Funktionsweise der $ watch-Methode: Wenn watchExpression aufgerufen wird, wie eckig die Ergebnisse verglichen werden usw.

Artem Andreev
quelle
4

wie wäre es mit:

scope.$watch(function() { 
   return { 
      a: thing-one, 
      b: thing-two, 
      c: red-fish, 
      d: blue-fish 
   }; 
}, listener...);
Wacamo
quelle
2
Ohne den dritten trueParameter to to scope.$watch(wie in Ihrem Beispiel) listener()würde jedes Mal ausgelöst, da der anonyme Hash jedes Mal eine andere Referenz hat.
Peter V. Mørch
Ich fand diese Lösung für meine Situation geeignet. Für mich war es eher wie : return { a: functionA(), b: functionB(), ... }. Ich überprüfe dann die zurückgegebenen Objekte der neuen und alten Werte gegeneinander.
RevNoah
4
$scope.$watch('age + name', function () {
  //called when name or age changed
});

Hier wird die Funktion aufgerufen, wenn sowohl Alter als auch Namenswert geändert werden.

Akash Shinde
quelle
2
Stellen Sie in diesem Fall auch sicher, dass Sie nicht die Argumente newValue und oldValue verwenden, die eckig in den Rückruf übergehen, da dies die resultierende Verkettung ist und nicht nur das Feld, das geändert wurde
Akash Shinde
1

$watchGroupIn Version 1.3 eingeführter Winkel, mit dem wir mehrere Variablen überwachen können, wobei ein einzelner $watchGroupBlock $watchGroupdas Array als ersten Parameter verwendet, in den wir alle zu überwachenden Variablen aufnehmen können.

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