Obejrzyj wiele atrybutów $ scope

442

Czy istnieje sposób na subskrybowanie wydarzeń na wielu obiektach za pomocą $watch

Na przykład

$scope.$watch('item1, item2', function () { });
Greg
źródło

Odpowiedzi:

586

Począwszy od AngularJS 1.3 istnieje nowa metoda wywołana $watchGroupdo obserwowania zestawu wyrażeń.

$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
źródło
7
jaka jest różnica w stosunku do $ watchCollection ('[a, b]') ??
Kamil Tomšík
28
Różnica polega na tym, że można użyć odpowiedniej tablicy zamiast ciągu, który wygląda jak tablica
Paolo Moretti
2
Miałem podobny problem, ale używałem wzorca ControllerAs. W moim przypadku poprawka polegała na dodaniu do zmiennych nazwy kontrolera:$scope.$watchGroup(['mycustomctrl.foo', 'mycustomctrl.bar'],...
morels
1
To nie wydaje mi się działać w Angular 1.6. Jeśli włączę dziennik konsoli do funkcji, widzę, że działa ona tylko raz za pomocą newValuesi oldValuesas undefined, podczas indywidualnego oglądania właściwości działają jak zwykle.
Alejandro García Iglesias
290

Począwszy od AngularJS 1.1.4 możesz używać $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
});

Przykład plunkera tutaj

Dokumentacja tutaj

Răzvan Flavius ​​Panda
źródło
109
Zauważ, że pierwszy parametr nie jest tablicą. To ciąg, który wygląda jak tablica.
MK Safi
1
dzięki za to. jeśli to zadziała, dam ci tysiąc złotych monet - oczywiście wirtualnych :)
AdityaSaxena
5
Wyjaśnienie (zajęło mi kilka minut uświadomienie sobie) $watchCollectionma na celu przekazanie obiektu i obserwowanie zmian w dowolnych właściwościach obiektu, podczas gdy w $watchGroupzamierzeniu ma być przekazany szereg indywidualnych właściwości do obserwowania zmian. Nieco inaczej, aby rozwiązać podobne, ale różne problemy. Uff!
Mattygabe
1
Jakoś nowe wartości i stare wartości są zawsze równe, gdy używasz tej metody: plnkr.co/edit/SwwdpQcNf78pQ80hhXMr
mostruash
Dzięki. To rozwiązało mój problem. Czy jest jakiś błąd w wydajności / problem z używaniem $ watch?
Syed Nasir Abbas
118

$watch Pierwszy parametr może być również funkcją.

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

Jeśli dwie połączone wartości są proste, pierwszy parametr jest normalnie wyrażeniem kątowym. Na przykład firstName i lastName:

$scope.$watch('firstName + lastName', function() {
  //stuff
});
Andrew Joslin
źródło
8
Wygląda to na przypadek użycia, który domyślnie woła o włączenie do frameworka. Nawet obliczona własność tak prosta, jak fullName = firstName + " " + lastNamewymaga przekazania funkcji jako pierwszego argumentu (zamiast prostego wyrażenia, takiego jak 'firstName, lastName'). Angular jest tak potężny, ale jest kilka obszarów, w których może być niezwykle przyjazny dla programistów w sposób, który (nie wydaje się) narażać na szwank wydajności lub zasad projektowania.
XML
4
No cóż, $ watch po prostu przyjmuje wyraz kątowy, który jest oceniany w zakresie, w jakim jest wywoływany. Więc możesz to zrobić $scope.$watch('firstName + lastName', function() {...}). Można też po prostu zrobić dwa zegarki: function nameChanged() {}; $scope.$watch('firstName', nameChanged); $scope.$watch('lastName', nameChanged);. Do odpowiedzi dodałem prosty przypadek.
Andrew Joslin
Plus w „firstName + lastName” to konkatenacja łańcuchów. Zatem kątowy sprawdza tylko, czy wynik konkatenacji jest teraz inny.
mb21
16
Łączenie łańcuchów wymaga nieco uwagi - jeśli zmienisz „paran orman” na „para norman”, nie wywołasz odpowiedzi; najlepiej umieścić dzielnik, taki jak „\ t | \ t” między pierwszym i drugim
semestrem
chociaż Amberjs jest do bani, ale ma tę wbudowaną funkcję! słyszę tych kanciastych facetów. Myślę, że robienie zegarków jest najbardziej racjonalnym wykonalnym sposobem.
Aladdin Mhemed
70

Oto rozwiązanie bardzo podobne do oryginalnego pseudokodu, które faktycznie działa:

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

EDYCJA: OK, myślę, że jest jeszcze lepiej:

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

Zasadniczo pomijamy krok Jsona, co na początku wydawało się głupie, ale bez niego nie działałoby. Klucz to często pomijany trzeci parametr, który włącza równość obiektów w przeciwieństwie do równości odniesienia. Wtedy porównania między naszymi utworzonymi obiektami tablicowymi faktycznie działają poprawnie.

Karen Zilles
źródło
Miałem podobne pytanie. I to jest dla mnie poprawna odpowiedź.
Calvin Cheng
Oba mają niewielki narzut wydajności, jeśli elementy w wyrażeniu tablicowym nie są typami prostymi.
null
To prawda… dokonuje pełnego porównania obiektów składowych. Który może, ale nie musi, być tym, czego chcesz. Zobacz aktualnie zatwierdzoną odpowiedź dla wersji z nowoczesnym kątem, która nie wykonuje pełnego porównania głębokości.
Karen Zilles
Z jakiegoś powodu, gdy próbowałem użyć $ watchCollection na tablicy, dostałem ten błąd, TypeError: Object #<Object> has no method '$watchCollection'ale to rozwiązanie pomaga mi rozwiązać mój problem!
abottoni
Fajnie, możesz nawet oglądać wartości w obiektach:$scope.$watch('[chaptercontent.Id, anchorid]', function (newValues, oldValues) { ... }, true);
Serge van den Oever
15

Możesz użyć funkcji w $ watchGroup, aby wybrać pola obiektu w zakresie.

        $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
źródło
1
Dlaczego używacie _this? Czy zakres nie jest przenoszony do funkcji bez wyraźnego zdefiniowania go?
sg.cc
1
Używam maszynopisu, prezentowany kod jest skompilowanym wynikiem.
Yang Zhang
12

Dlaczego nie po prostu owinąć to w forEach?

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

To mniej więcej tyle samo, co zapewnienie funkcji dla połączonej wartości, bez faktycznego martwienia się o skład wartości .

Erik Aigner
źródło
W takim przypadku log () byłby wykonywany kilka razy, prawda? Myślę, że w przypadku zagnieżdżonych funkcji $ watch nie byłoby. I nie powinno w rozwiązaniu oglądać kilku atrybutów jednocześnie.
John Doe
Nie, dziennik jest wykonywany tylko raz na zmianę dla obserwowanej właściwości. Dlaczego miałby być wywoływany wiele razy?
Erik Aigner
Racja, mam na myśli, że nie działałoby to dobrze, gdybyśmy chcieli oglądać je wszystkie jednocześnie.
John Doe
1
Jasne, czemu nie? Robię to w mój projekt. changed()jest wywoływany za każdym razem, gdy coś zmienia się w którejkolwiek z właściwości. To jest dokładnie to samo zachowanie, jakby podano funkcję dla połączonej wartości.
Erik Aigner
1
Różni się to nieco tym, że jeśli wiele elementów w tablicy zmienia się w tym samym czasie, zegarek $ uruchomi obsługę tylko raz zamiast raz na zmianę elementu.
bingles
11

Nieco bezpieczniejszym rozwiązaniem łączenia wartości może być użycie następujących $watchfunkcji:

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

lub

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

Pierwszy parametr $ watch może być wyrażeniem kątowym lub funkcją. Zobacz dokumentację dotyczącą $ scope. $ Watch . Zawiera wiele użytecznych informacji o tym, jak działa metoda $ watch: kiedy wywoływana jest funkcja watchExpression, sposób porównywania wyników przez kąt itp.

Artem Andreev
źródło
4

Co powiesz na:

scope.$watch(function() { 
   return { 
      a: thing-one, 
      b: thing-two, 
      c: red-fish, 
      d: blue-fish 
   }; 
}, listener...);
wacamo
źródło
2
Bez trzeciego trueparametru to scope.$watch(jak w twoim przykładzie) listener()uruchamiany byłby za każdym razem, ponieważ anonimowy skrót ma za każdym razem inne odniesienie.
Peter V. Mørch
Odkryłem, że to rozwiązanie zadziałało w mojej sytuacji. Dla mnie to było bardziej jak: return { a: functionA(), b: functionB(), ... }. Następnie sprawdzam względem siebie zwrócone obiekty nowych i starych wartości.
RevNoah
4
$scope.$watch('age + name', function () {
  //called when name or age changed
});

Tutaj funkcja zostanie wywołana, gdy zmieni się zarówno wiek, jak i imię.

Akash Shinde
źródło
2
Upewnij się również, że w tym przypadku nie używaj argumentów newValue i oldValue, które kąt przenosi do wywołania zwrotnego, ponieważ będą one wynikową konkatenacją, a nie tylko zmienionym polem
Akash Shinde
1

Angular wprowadzony $watchGroupw wersji 1.3, za pomocą którego możemy oglądać wiele zmiennych, z jednym $watchGroupblokiem $watchGroupprzyjmuje tablicę jako pierwszy parametr, w którym możemy uwzględnić wszystkie nasze zmienne do oglądania.

$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
źródło