Jak korzystać z $ scope. $ Watch i $ scope. $ Mają zastosowanie w AngularJS?

1088

Nie rozumiem, jak używać $scope.$watchi $scope.$apply. Oficjalna dokumentacja nie jest pomocna.

Czego konkretnie nie rozumiem:

  • Czy są połączone z DOM?
  • Jak mogę zaktualizować zmiany DOM w modelu?
  • Jaki jest punkt połączenia między nimi?

Próbowałem tego samouczka , ale wymaga to zrozumienia $watchi $applypewności.

Co robią $applyi co $watchrobią i jak z nich odpowiednio korzystać?

ilyo
źródło

Odpowiedzi:

1737

Musisz wiedzieć, jak działa AngularJS, aby to zrozumieć.

Cykl trawienia i zakres $

Przede wszystkim AngularJS definiuje koncepcję tak zwanego cyklu trawienia . Cykl ten można uznać za pętlę, podczas której AngularJS sprawdza, czy są jakieś zmiany we wszystkich zmiennych obserwowanych przez wszystkie $scopes. Jeśli więc $scope.myVarzdefiniowałeś w kontrolerze i ta zmienna została oznaczona do oglądania , to domyślnie mówisz AngularJS, aby monitorował zmiany myVarw każdej iteracji pętli.

Naturalnym następczym pytaniem byłoby: czy wszystko jest związane z $scopeoglądaniem? Na szczęście nie. Jeśli będziesz obserwować zmiany w każdym obiekcie w swoim $scopewnętrzu, to szybko zajmie to całe wieki, aby ocenić i szybko wpadniesz na problemy z wydajnością. Dlatego zespół AngularJS dał nam dwa sposoby zadeklarowania $scopezmiennej jako obserwowanej (czytaj poniżej).

$ watch pomaga nasłuchiwać zmian zakresu $

Istnieją dwa sposoby zadeklarowania $scopezmiennej jako obserwowanej.

  1. Używając go w szablonie za pomocą wyrażenia <span>{{myVar}}</span>
  2. Dodając go ręcznie za pośrednictwem $watchusługi

Ad 1) Jest to najczęstszy scenariusz i jestem pewien, że widziałeś go wcześniej, ale nie wiedziałeś, że stworzył zegarek w tle. Tak było! Korzystanie z dyrektyw AngularJS (np. ng-repeat) Może również tworzyć niejawne zegarki.

Ad 2) W ten sposób tworzysz własne zegarki . $watchUsługa pomaga uruchomić kod, gdy $scopezmieni się pewna wartość dołączona do pliku. Jest rzadko używany, ale czasem jest pomocny. Na przykład, jeśli chcesz uruchomić jakiś kod za każdym razem, gdy zmienia się „myVar”, możesz wykonać następujące czynności:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$ Apply umożliwia zintegrowanie zmian z cyklem podsumowania

Możesz myśleć o tej $applyfunkcji jak o mechanizmie integracji . Widzisz, za każdym razem, gdy zmieniasz obserwowaną zmienną dołączoną$scope bezpośrednio do obiektu, AngularJS będzie wiedział, że zmiana nastąpiła. Jest tak, ponieważ AngularJS już wiedział, że monitoruje te zmiany. Jeśli więc zdarzy się to w kodzie zarządzanym przez platformę, cykl podsumowania będzie kontynuowany.

Czasami jednak chcesz zmienić jakąś wartość poza światem AngularJS i zobaczyć, jak zmiany propagują się normalnie. Zastanów się - masz $scope.myVarwartość, która zostanie zmodyfikowana w module $.ajax()obsługi jQuery . Stanie się to w pewnym momencie w przyszłości. AngularJS nie może się doczekać, aby tak się stało, ponieważ nie polecono mu czekać na jQuery.

Aby rozwiązać ten problem, $applyzostał wprowadzony. Umożliwia jawne rozpoczęcie cyklu trawienia. Powinieneś jednak użyć tego tylko do migracji niektórych danych do AngularJS (integracja z innymi frameworkami), ale nigdy nie używaj tej metody w połączeniu ze zwykłym kodem AngularJS, ponieważ AngularJS zgłosi błąd.

Jak to wszystko ma się do DOM?

Cóż, powinieneś naprawdę postępować zgodnie z samouczkiem, skoro wiesz już wszystko. Cykl podsumowania upewni się, że interfejs użytkownika i kod JavaScript pozostaną zsynchronizowane, oceniając każdego obserwatora podłączonego do wszystkich, $scopeo ile nic się nie zmieni. Jeśli w pętli podsumowania nie nastąpią już żadne zmiany, uznaje się to za zakończone.

Możesz dołączyć obiekty do $scopeobiektu albo jawnie w kontrolerze, albo zadeklarując je w {{expression}}formie bezpośrednio w widoku.

Mam nadzieję, że pomoże to wyjaśnić podstawową wiedzę na ten temat.

Dalsze odczyty:

ŁukaszBachman
źródło
57
„Angular sprawdza, czy są jakieś zmiany we wszystkich zmiennych dołączonych do wszystkich $ zakresów” - nie sądzę, żeby to było całkiem właściwe. Wierzę, że tylko Angular (brudny) sprawdza właściwości $ scope, dla których skonfigurowano $ zegarki (pamiętaj, że użycie {{}} w widoku automatycznie utworzy zegarek $). Zobacz także sekcję „Zakres $ Oglądaj Uwagi dotyczące wydajności” na stronie Zakres .
Mark Rajcok
5
Tak może być. Postaram się znaleźć trochę czasu, aby przeczytać o tym więcej i edytować swoją odpowiedź.
ŁukaszBachman
15
@MarkRajcok, miałeś rację. Zmieniłem odpowiedź i wskazałem artykuł, który ładnie pokazuje, jak to się realizuje.
ŁukaszBachman
3
co z korzystaniem z tego? (Metoda „Control as”)
Leandro,
2
Używanie „Kontroluj jako” nie powinno mieć wpływu na powyższe informacje. Korzystanie z this.myVar nakłada myVar na zakres.
Marcus Rådell
161

W AngularJS aktualizujemy nasze modele, a nasze widoki / szablony aktualizują DOM „automatycznie” (poprzez wbudowane lub niestandardowe dyrektywy).

$ Apply i $ watch, obie metody Scope, nie są powiązane z DOM.

Strona Pojęcia (sekcja „ Środowisko wykonawcze”) zawiera całkiem dobre objaśnienie pętli $ digest, $ Apply, kolejki $ evalAsync i listy obserwowanych $. Oto zdjęcie towarzyszące tekstowi:

$ digest loop

Jakikolwiek kod ma dostęp do zakresu - zwykle kontrolery i dyrektywy (ich funkcje łącza i / lub ich kontrolery) - mogą ustawić „ watchExpression ”, które AngularJS oceni na podstawie tego zakresu. Ta ocena ma miejsce za każdym razem, gdy AngularJS wejdzie do swojej pętli $ streszczenia (w szczególności pętli „$ watch list”). Możesz oglądać indywidualne właściwości zakresu, możesz zdefiniować funkcję, aby oglądać dwie właściwości razem, możesz obserwować długość tablicy itp.

Kiedy coś się dzieje „wewnątrz AngularJS” - np. Wpisujesz w polu tekstowym, które ma włączone dwukierunkowe wiązanie danych AngularJS (tj. Używa modelu ng), następuje wywołanie zwrotne $ http itp. - $ Apply zostało już wywołane, więc znajdują się w prostokącie „AngularJS” na powyższym rysunku. Wszystkie wyrażenia watch będą oceniane (być może więcej niż jeden raz - dopóki nie zostaną wykryte dalsze zmiany).

Kiedy coś się dzieje „poza AngularJS” - np. Użyłeś bind () w dyrektywie, a następnie to zdarzenie zostaje wywołane, co powoduje wywołanie twojego wywołania zwrotnego lub niektóre wywołania wywołania zwrotnego zarejestrowane przez jQuery - wciąż jesteśmy w „rodzimym” prostokącie. Jeśli kod zwrotny modyfikuje cokolwiek, co ogląda dowolny zegarek $, wywołanie $ stosuje się, aby dostać się do prostokąta AngularJS, powodując uruchomienie pętli $ digest, a zatem AngularJS zauważy zmianę i wykona swoją magię.

Mark Rajcok
źródło
5
Rozumiem pomysł, ale nie rozumiem, w jaki sposób dane są faktycznie przesyłane. Mam model, który jest obiektem z dużą ilością danych, używam niektórych z nich do manipulowania DOM. potem niektóre z nich ulegają zmianie. Jak umieścić zmienione dane we właściwym miejscu w modelu? W przykładzie, którego użyłem, on dokonuje manipulacji, a ostatecznie po prostu używa scope.$apply(scope.model), nie rozumiem, jakie dane są przesyłane i jak są przenoszone we właściwe miejsce w modelu?
ilyo
6
Nie ma miejsca magiczny transfer danych. Zwykle w aplikacjach Angular należy zmienić modele Angular, które następnie napędzają aktualizacje widoku / DOM. Jeśli zaktualizujesz DOM poza Angular, będziesz musiał ręcznie zaktualizować modele. scope.$apply(scope.model)będzie po prostu oceniać scope.modeljako wyrażenie kątowe, a następnie wejdzie w pętlę $ digest. W artykule, do którego się odwołujesz, prawdopodobnie scope.$apply()byłby wystarczający, ponieważ model jest już oglądany. Funkcja stop () aktualizuje model (uważam, że toUpdate jest odniesieniem do scope.model), a następnie wywoływane jest $ Apply.
Mark Rajcok
Wygląda na to, że dokumenty AngularJS przesunęły się spod tej odpowiedzi (pierwszy link nie ma „środowiska $watchuruchomieniowego ” lub na stronie, a drugi link jest uszkodzony - jak na razie). Boleśnie wersje archiwalne nie buforowały żadnego procesu asynchronicznego tworzącego treść.
ruffin
52

AngularJS rozszerza tę pętlę zdarzeń , tworząc coś o nazwie AngularJS context.

$ watch ()

Za każdym razem, gdy wiążą się coś w interfejsie włożysz $watchw $watchliście .

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

Tutaj mamy $scope.user, który jest związany z pierwszym wejściem, i mamy $scope.pass, który jest powiązany z drugim wejściem . W ten sposób dodajemy dwie listy $watchdo $watchlisty .

Kiedy nasz szablon zostanie załadowany, AKA w fazie łączenia, kompilator wyszuka każdą dyrektywę i utworzy wszystkie $watchpotrzebne.

Angularjs zapewnia $watch, $watchcollectioni $watch(true). Poniżej znajduje się zgrabny schemat wyjaśniający wszystkie trzy wzięte głęboko od obserwatorów .

Wpisz opis zdjęcia tutaj

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest pętla

Gdy przeglądarka odbierze zdarzenie, którym można zarządzać w kontekście AngularJS, $digestpętla zostanie uruchomiona . Ta pętla składa się z dwóch mniejszych pętli. Jeden przetwarza $evalAsynckolejkę, a drugi przetwarza $watch list. $digestPętla poprzez liście $watch, że mamy

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

Tutaj mamy tylko jeden, $watchponieważ ng-click nie tworzy żadnych zegarków.

Naciskamy przycisk.

  1. Przeglądarka odbiera zdarzenie, które wejdzie w kontekst AngularJS
  2. $digestPętla zostanie uruchomiony i poprosi każde $ obserwować zmiany.
  3. Ponieważ ten, $watchktóry obserwował zmiany w $ scope.name zgłasza zmianę, wymusi kolejną $digestpętlę.
  4. Nowa pętla nic nie zgłasza.
  5. Przeglądarka odzyskuje kontrolę i zaktualizuje DOM odzwierciedlając nową wartość $ scope.name
  6. Ważną rzeczą jest to, że KAŻDE zdarzenie, które wejdzie w kontekst AngularJS, uruchomi $digestpętlę. Oznacza to, że za każdym razem, gdy piszemy literę w danych wejściowych, pętla będzie sprawdzać każde $watchna tej stronie.

Zastosuj $ ()

Jeśli zadzwonisz, $applygdy zostanie wywołane zdarzenie, przejdzie ono przez kontekst kątowy, ale jeśli go nie wywołasz, będzie działać poza nim. To takie proste. $applywywoła$digest() pętlę wewnętrznie i wykona iterację po wszystkich zegarkach, aby upewnić się, że DOM został zaktualizowany o nowo zaktualizowaną wartość.

$apply()Metoda spowoduje obserwatorów na całym $scopełańcuchu natomiast $digest()metoda spowoduje jedynie obserwatorów na prąd $scopei jej children. Gdy żaden z wyżej wymienionych $scopeobiektów nie musi wiedzieć o lokalnych zmianach, możesz użyć $digest().

Thalaivar
źródło
18

I okazało się bardzo filmów pogłębione który pokrywa $watch, $apply, $digesti strawienia w cyklach:

Poniżej znajduje się kilka slajdów używanych w tych filmach w celu wyjaśnienia pojęć (na wszelki wypadek, jeśli powyższe linki zostaną usunięte / nie działają).

Wpisz opis zdjęcia tutaj

Na powyższym obrazie „$ scope.c” nie jest obserwowany, ponieważ nie jest używany w żadnym powiązaniu danych (w znacznikach). Pozostałe dwa ( $scope.ai $scope.b) będą oglądane.

Wpisz opis zdjęcia tutaj

Z powyższego obrazu: W oparciu o odpowiednie zdarzenie przeglądarki, AngularJS przechwytuje zdarzenie, wykonuje cykl podsumowania (przechodzi wszystkie zegarki w celu wprowadzenia zmian), wykonuje funkcje zegarka i aktualizuje DOM. Jeśli nie są to zdarzenia przeglądarki, cykl podsumowania można uruchomić ręcznie za pomocą $applylub $digest.

Więcej o $applyi $digest:

Wpisz opis zdjęcia tutaj

użytkownik203687
źródło
17

$watchGroupi $watchCollectionrównież. W szczególności, $watchGroupjest bardzo pomocne, jeśli chcemy wywołać funkcję zaktualizować obiekt, który ma wiele właściwości w widoku, który nie jest Dom przedmiot, na przykład inny pogląd na płótnie, WebGL czy żądanie serwera.

Tutaj link do dokumentacji .

Utkarsh Bhardwaj
źródło
Skomentowałbym to, $watchCollectionale widzę, że już to zrobiłeś. Oto dokumentacja na ten temat ze strony AngularJS. Zapewniają bardzo ładny obraz $watchgłębi. Uwaga: informacje znajdują się w dolnej części strony.
JabberwockyDecompiler
15

Po prostu przestań czytać WSZYSTKIE powyższe, nudne i senne (przepraszam, ale to prawda). Bardzo techniczny, dogłębny, szczegółowy i suchy. Dlaczego piszę Ponieważ AngularJS jest ogromny, wiele wzajemnie połączonych koncepcji może doprowadzić do szału każdego. Często zadawałem sobie pytanie, czy nie jestem wystarczająco inteligentny, aby je zrozumieć? Nie! To dlatego, że tak niewielu potrafi wytłumaczyć technologię w języku for-dummie bez wszystkich terminologii! OK, pozwól mi spróbować:

1) Wszystkie są rzeczami napędzanymi zdarzeniami. (Słyszę śmiech, ale czytam dalej)

Jeśli nie wiesz, co to jest zdarzenie sterowane, a następnie pomyśl, że umieścisz przycisk na stronie, podłącz go z funkcją za pomocą „kliknięcia”, czekając, aż użytkownicy klikną go, aby wywołać działania, które umieścisz w funkcjonować. Albo pomyśl o „wyzwalaczu” SQL Server / Oracle.

2) $ watch to „po kliknięciu”.

Na szczególną uwagę zasługuje to, że jako parametry przyjmuje się 2 funkcje, pierwsza podaje wartość ze zdarzenia, druga bierze pod uwagę wartość ...

3) $ digest to szef, który niestrudzenie się rozgląda , bla-bla-bla, ale dobry szef.

4) $ Apply daje ci sposób, kiedy chcesz to zrobić ręcznie , jak w przypadku zabezpieczenia przed awarią (w przypadku, gdy kliknięcie nie uruchamia się, wymuszasz jego uruchomienie).

Teraz zróbmy to wizualnie. Wyobraź to, aby jeszcze łatwiej zrozumieć ten pomysł:

W restauracji,

- CZEKAJ

mają przyjmować zamówienia od klientów, to jest

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- MANAGER biegnie, aby upewnić się, że wszyscy kelnerzy nie śpią i reagują na wszelkie oznaki zmian ze strony klientów. To jest$digest()

- WŁAŚCICIEL ma najwyższą moc, aby poprowadzić wszystkich na żądanie, to jest$apply()

Jeb50
źródło
2
Może to zrozumieć pięciolatek. Doceniam tego rodzaju odpowiedź. +1
Chris22