1131 Stimmen

Wie verwende ich $scope.$watch und $scope.$apply in AngularJS?

Ich verstehe nicht, wie man $scope.$watch und $scope.$apply benutzt. Die offizielle Dokumentation ist nicht hilfreich.

Was ich speziell nicht verstehe:

  • Sind sie mit dem DOM verbunden?
  • Wie kann ich DOM-Änderungen am Modell aktualisieren?
  • Was ist der Verbindungspunkt zwischen ihnen?

Ich habe dieses Tutorial ausprobiert, aber es setzt das Verständnis von $watch und $apply voraus.

Was machen $apply und $watch, und wie verwende ich sie angemessen?

1790voto

ŁukaszBachman Punkte 33457

Sie müssen sich darüber im Klaren sein, wie AngularJS funktioniert, um es zu verstehen.

Digest-Zyklus und $scope

Zunächst definiert AngularJS ein Konzept eines sogenannten Digest-Zyklus. Dieser Zyklus kann als eine Art Schleife betrachtet werden, während der AngularJS überprüft, ob es Änderungen an allen Variablen gibt, die von allen $scopes überwacht werden. Wenn also $scope.myVar in Ihrem Controller definiert ist und diese Variable als überwacht markiert wurde, teilen Sie AngularJS implizit mit, die Änderungen an myVar in jeder Iteration der Schleife zu überwachen.

Eine naheliegende Frage wäre: Wird alles, was an $scope angehängt ist, überwacht? Glücklicherweise nein. Wenn Sie nach Änderungen an jedem Objekt in Ihrem $scope suchen würden, würde eine Digest-Schleife schnell lange dauern, um ausgewertet zu werden, und Sie würden schnell auf Leistungsprobleme stoßen. Deshalb hat uns das AngularJS-Team zwei Möglichkeiten gegeben, eine $scope-Variable als überwacht zu deklarieren (lesen Sie unten).

$watch hilft, auf $scope-Änderungen zu hören

Es gibt zwei Möglichkeiten, eine $scope-Variable als überwacht zu deklarieren.

  1. Indem Sie es in Ihrem Template über den Ausdruck {{myVar}} verwenden
  2. Indem Sie es manuell über den $watch-Service hinzufügen

Ad 1) Dies ist das häufigste Szenario und ich bin sicher, Sie haben es bereits gesehen, wussten aber nicht, dass dadurch im Hintergrund eine Überwachung erstellt wurde. Ja, das war der Fall! Die Verwendung von AngularJS-Direktiven (wie z.B. ng-repeat) kann auch implizite Überwachungen erstellen.

Ad 2) So erstellen Sie Ihre eigenen Überwachungen. Der $watch-Service hilft Ihnen dabei, Code auszuführen, wenn sich ein Wert, der an den $scope angehängt ist, geändert hat. Es wird selten verwendet, ist aber manchmal hilfreich. Wenn Sie beispielsweise jedes Mal Code ausführen möchten, wenn sich 'myVar' ändert, könnten Sie folgendes tun:

function MyController($scope) {

    $scope.myVar = 1;

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

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // Dadurch wird der Ausdruck $watch aktiviert
    };
}

$apply ermöglicht die Integration von Änderungen in den Digest-Zyklus

Sie können die Funktion $apply als Integrationsmechanismus betrachten. Jedes Mal, wenn Sie eine überwachte Variable, die an das $scope-Objekt angehängt ist, direkt ändern, wird AngularJS wissen, dass die Änderung stattgefunden hat. Dies liegt daran, dass AngularJS bereits wusste, diese Änderungen zu überwachen. Wenn dies im vom Framework verwalteten Code passiert, wird der Digest-Zyklus fortgesetzt.

Manchmal möchten Sie jedoch einen Wert außerhalb der AngularJS-Welt ändern und die Änderungen normal propagieren sehen. Angenommen, Sie haben einen Wert $scope.myVar, der innerhalb eines jQuery-$.ajax()-Handlers geändert wird. Dies wird irgendwann in der Zukunft passieren. AngularJS kann nicht auf das Warten, da es nicht angewiesen wurde, auf jQuery zu warten.

Dafür wurde $apply eingeführt. Es ermöglicht Ihnen, den Digestionszyklus explizit zu starten. Sie sollten dies jedoch nur verwenden, um Daten zu AngularJS zu migrieren (Integration mit anderen Frameworks), aber niemals diese Methode mit normalem AngularJS-Code kombinieren, da AngularJS sonst einen Fehler ausgibt.

Wie hängt das alles mit dem DOM zusammen?

Nun sollten Sie das Tutorial noch einmal durchgehen, jetzt, da Sie all dies wissen. Der Digest-Zyklus stellt sicher, dass die Benutzeroberfläche und der JavaScript-Code synchron bleiben, indem er in jeder Iteration aller an alle $scopes angehängten Beobachter auswertet, solange nichts passiert. Wenn keine weiteren Änderungen im Digest-Zyklus auftreten, gilt dieser als beendet.

Sie können Objekte entweder explizit im Controller an das $scope-Objekt anhängen oder sie direkt im View-Template in {{expression}}-Form deklarieren.

Weitere Informationen:

3 Stimmen

Was ist mit der Verwendung von diesem? ("Control as" Methode)

2 Stimmen

Unter Verwendung von "Control as" sollte keinen Einfluss auf die oben stehenden Informationen haben. Durch die Verwendung von this.myVar wird myVar im Scope platziert.

2 Stimmen

@ ukaszBachman - "dann sagst du Angular explizit, die Änderungen zu überwachen". Ich glaube, es heißt 'implizit' und nicht 'explizit'.

164voto

Mark Rajcok Punkte 356006

In AngularJS aktualisieren wir unsere Modelle, und unsere Ansichten/Vorlagen aktualisieren das DOM "automatisch" (über eingebaute oder benutzerdefinierte Direktiven).

$apply und $watch, die beide Scope-Methoden sind, haben nichts mit dem DOM zu tun.

Die Konzepte Seite (Abschnitt "Laufzeit") enthält eine recht gute Erklärung der $digest-Schleife, $apply, der $evalAsync Warteschlange und der $watch-Liste. Hier ist das Bild, das dem Text beigefügt ist:

$digest loop

Jeder Code, der auf einen Scope zugreifen kann - normalerweise Controller und Direktiven (ihre Link-Funktionen und/oder ihre Controller) - kann eine "watchExpression" einrichten, die von AngularJS gegen diesen Scope ausgewertet wird. Diese Auswertung erfolgt immer dann, wenn AngularJS seine $digest-Schleife durchläuft (insbesondere die "$watch-Liste"-Schleife). Sie können einzelne Scope-Eigenschaften überwachen, eine Funktion definieren, um zwei Eigenschaften zusammen zu überwachen, die Länge eines Arrays überwachen usw.

Wenn Dinge "innerhalb von AngularJS" passieren - z.B. wenn Sie in ein Textfeld eingeben, das AngularJS-Zweiwege-Datenbindung aktiviert hat (d.h. ng-model verwendet), ein $http-Callback ausgelöst wird usw. - wurde $apply bereits aufgerufen, sodass wir uns im "AngularJS"-Rechteck in der Abbildung oben befinden. Alle watchExpressions werden ausgewertet (möglicherweise mehrmals - bis keine weiteren Änderungen festgestellt werden).

Wenn Dinge "außerhalb von AngularJS" passieren - z.B. wenn Sie in einer Direktive bind() verwendet haben und dann dieses Ereignis auslöst, wodurch Ihr Callback aufgerufen wird, oder ein von jQuery registrierter Callback ausgelöst wird - befinden wir uns immer noch im "Native" Rechteck. Wenn der Callback-Code etwas ändert, das von einem $watch überwacht wird, rufen Sie $apply auf, um in das AngularJS-Rechteck zu gelangen, was dazu führt, dass die $digest-Schleife durchgeführt wird, und anschließend bemerkt AngularJS die Änderung und führt seine Magie aus.

6 Stimmen

Ich verstehe die Idee, was ich nicht verstehe, ist, wie die Daten tatsächlich übertragen werden. Ich habe ein Modell, das ein Objekt mit vielen Daten ist, von denen ich einige verwende, um das DOM zu manipulieren. Dann werden einige davon geändert. Wie bringe ich die geänderten Daten an die richtige Stelle im Modell? Im Beispiel, das ich verwendet habe, führt er die Manipulation durch und verwendet am Ende einfach scope.$apply(scope.model), ich verstehe nicht, welche Daten übertragen werden und wie sie an die richtige Stelle im Modell übertragen werden.

6 Stimmen

Es findet kein magischer Datentransfer statt. Normalerweise sollten Sie bei Angular-Apps die Angular-Modelle ändern, die dann die Ansichts-/DOM-Updates steuern. Wenn Sie das DOM außerhalb von Angular aktualisieren, müssen Sie die Modelle manuell aktualisieren. scope.$apply(scope.model) wird einfach scope.model als Angular-Ausdruck auswerten und dann eine $digest-Schleife starten. In dem von Ihnen referenzierten Artikel wäre wahrscheinlich scope.$apply() ausreichend, da das Modell bereits $watch'ed wird. Die stop()-Funktion aktualisiert das Modell (ich glaube, toUpdate ist eine Referenz auf scope.model) und dann wird $apply aufgerufen.

0 Stimmen

Es sieht so aus, als ob die AngularJS-Dokumentation unter dieser Antwort verschoben wurde (der erste Link enthält weder "Laufzeit" noch $watch auf der Seite, und der zweite Link ist defekt - zumindest im Moment). Leider hat die Archivversion den Inhalt nicht zwischengespeichert, der durch einen asynchronen Prozess erstellt wurde.

58voto

Thalaivar Punkte 22174

AngularJS erweitert diesen Ereignis-Loop und erstellt etwas namens AngularJS-Kontext.

$watch()

Jedes Mal, wenn Sie etwas im UI binden, fügen Sie eine $watch-Liste ein.

Benutzer: 
Passwort: 

Hier haben wir $scope.user, das an die erste Eingabe gebunden ist, und wir haben $scope.pass, das an die zweite Eingabe gebunden ist. Dadurch fügen wir zwei $watchs zur $watch-Liste hinzu.

Wenn unser Template geladen wird, auch bekannt als in der Verknüpfungsphase, sucht der Compiler nach jeder Direktive und erstellt alle benötigten $watchs.

AngularJS bietet $watch, $watchcollection und $watch(true). Unten ist ein übersichtliches Diagramm, das alle drei erklärt, entnommen aus Watchern im Detail.

Geben Sie hier eine Bildbeschreibung ein

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("**** Referenzprüfer $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collectionsprüfer $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** Gleichheitsprüfer mit $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-Schleife

Wenn der Browser ein Ereignis empfängt, das vom AngularJS-Kontext verwaltet werden kann, wird die $digest-Schleife ausgeführt. Diese Schleife besteht aus zwei kleineren Schleifen. Eine bearbeitet die $evalAsync-Warteschlange, und die andere bearbeitet die $watch-Liste. Der $digest wird die Liste der $watchs durchlaufen, die wir haben.

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

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

{{ name }}
Ändere den Namen

Hier haben wir nur einen $watch, weil ng-click keine Watches erstellt.

Wir drücken den Knopf.

  1. Der Browser empfängt ein Ereignis, das in den AngularJS-Kontext eintritt
  2. Die $digest-Schleife wird ausgeführt und wird bei jedem $watch nach Änderungen fragen.
  3. Da der $watch, der Änderungen in $scope.name überwacht hat, eine Änderung meldet, wird er eine weitere $digest-Schleife erzwingen.
  4. Die neue Schleife meldet nichts.
  5. Der Browser erhält die Kontrolle zurück und aktualisiert den DOM, um den neuen Wert von $scope.name widerzuspiegeln
  6. Das Wichtige hier ist, dass JEDER im AngularJS-Kontext eintretende Event eine $digest-Schleife durchlaufen wird. Das bedeutet, dass jedes Mal, wenn wir einen Buchstaben in ein Eingabefeld schreiben, die Schleife alle $watchs auf dieser Seite überprüft.

$apply()

Wenn Sie $apply aufrufen, wenn ein Ereignis ausgelöst wird, wird es durch den Angular-Kontext gehen, aber wenn Sie es nicht aufrufen, wird es außerhalb davon ausgeführt. So einfach ist das. $apply ruft intern die Methode $digest() auf und wird über alle Watches iterieren, um sicherzustellen, dass der DOM mit dem neu aktualisierten Wert aktualisiert wird.

Die Methode $apply() wird Watcher auf der gesamten $scope-Kette auslösen, während die Methode $digest() nur Watcher im aktuellen $scope und seinen Kindern auslösen wird. Wenn keine der höherliegenden $scope-Objekte über lokale Änderungen Bescheid wissen muss, können Sie $digest() verwenden.

19voto

user203687 Punkte 6513

Ich habe sehr eingehende Videos gefunden, die $watch, $apply, $digest und Digest-Zyklen abdecken:

Hier sind ein paar Folien, die in diesen Videos verwendet wurden, um die Konzepte zu erklären (für den Fall, dass die obigen Links entfernt/nicht funktionstüchtig sind).

Geben Sie hier eine Bildbeschreibung ein

In dem obigen Bild wird "$scope.c" nicht überwacht, da es in keinem der Datenbindungen (im Markup) verwendet wird. Die anderen beiden ($scope.a und $scope.b) werden überwacht.

Geben Sie hier eine Bildbeschreibung ein

Aus dem obigen Bild: Basierend auf dem jeweiligen Browserereignis erfasst AngularJS das Ereignis, führt den Digest-Zyklus durch (geht durch alle Überwachungen auf Änderungen), führt Überwachungsfunktionen aus und aktualisiert das DOM. Wenn keine Browserereignisse vorliegen, kann der Digest-Zyklus manuell mit $apply oder $digest ausgelöst werden.

Mehr über $apply und $digest:

Geben Sie hier eine Bildbeschreibung ein

18voto

Utkarsh Bhardwaj Punkte 600

Es gibt auch $watchGroup und $watchCollection. Speziell ist $watchGroup wirklich hilfreich, wenn Sie eine Funktion aufrufen möchten, um ein Objekt zu aktualisieren, das mehrere Eigenschaften in einer Ansicht hat, die kein DOM-Objekt ist, z.B. eine andere Ansicht in Canvas, WebGL oder Serveranfrage.

Hier ist die Dokumentation Link.

0 Stimmen

Ich hätte gerne zum $watchCollection kommentiert, aber ich sehe, dass du das bereits getan hast. Hier ist die Dokumentation dazu von der AngularJS Website. Sie bieten eine sehr schöne Visualisierung der Tiefe des $watch. Beachte, dass die Informationen sich nahe am Ende der Seite befinden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X