Wie funktioniert die Datenbindung in AngularJS?

1956

Wie funktioniert die Datenbindung im AngularJSFramework?

Ich habe keine technischen Details auf ihrer Website gefunden . Es ist mehr oder weniger klar, wie es funktioniert, wenn Daten von der Ansicht zum Modell weitergegeben werden. Aber wie verfolgt AngularJS Änderungen der Modelleigenschaften ohne Setter und Getter?

Ich fand heraus, dass es JavaScript-Beobachter gibt , die diese Arbeit erledigen können. Sie werden jedoch in Internet Explorer 6 und Internet Explorer 7 nicht unterstützt . Woher weiß AngularJS, dass ich beispielsweise Folgendes geändert und diese Änderung in einer Ansicht wiedergegeben habe?

myobject.myproperty="new value";
Pashec
quelle
10
Beachten Sie, dass Sie seit Angular 1.0.0rc1 ng-model-instant ( docs-next.angularjs.org/api/… ) angeben müssen, damit Ihr Moderator sofort aktualisiert wird. Andernfalls wird es bei einem Unschärfeereignis aktualisiert.
Sotomajor
8
Marcellos Link ist anscheinend defekt, also hier ist es wieder: github.com/mhevery/angular.js/blob/master/docs/content/guide/…
Riffraff
6
@orian, dieser Link ist schlecht. aktualisiert auf (ich nehme an) ist das gleiche - docs.angularjs.org/guide/databinding
Kevin Meredith
11
Wenn Sie diese Frage noch lesen, beachten Sie bitte, dass Angular 2.0 die Art und Weise der Datenbindung seit Angular 1.x stark geändert hat, um mit Webkomponenten zu arbeiten und viele der in den folgenden Antworten aufgeführten Probleme zu beheben.
August

Antworten:

2744

AngularJS merkt sich den Wert und vergleicht ihn mit einem vorherigen Wert. Dies ist eine grundlegende Schmutzprüfung. Wenn sich der Wert ändert, wird das Änderungsereignis ausgelöst.

Die $apply()Methode, die Sie beim Übergang von einer Nicht-AngularJS-Welt in eine AngularJS-Welt aufrufen, wird aufgerufen $digest(). Ein Digest ist einfach nur eine alte Drecksprüfung. Es funktioniert in allen Browsern und ist vollständig vorhersehbar.

Im Gegensatz zu Dirty-Checking (AngularJS) und Change-Listenern ( KnockoutJS und Backbone.js ): Während Dirty-Checking einfach und sogar ineffizient erscheint (darauf werde ich später noch eingehen), stellt sich heraus, dass es immer semantisch korrekt ist. Während Change Listener viele seltsame Eckfälle haben und Dinge wie Abhängigkeitsverfolgung benötigen, um sie semantisch korrekter zu machen. KnockoutJS-Abhängigkeitsverfolgung ist eine clevere Funktion für ein Problem, das AngularJS nicht hat.

Probleme mit Änderungslistenern:

  • Die Syntax ist grausam, da Browser sie nicht nativ unterstützen. Ja, es gibt Proxys, aber sie sind nicht in allen Fällen semantisch korrekt, und natürlich gibt es in alten Browsern keine Proxys. Unter dem Strich können Sie mit Dirty-Checking POJO ausführen , während KnockoutJS und Backbone.js Sie zwingen, von ihren Klassen zu erben und über Accessoren auf Ihre Daten zuzugreifen.
  • Koaleszenz ändern. Angenommen, Sie haben eine Reihe von Elementen. Angenommen, Sie möchten einem Array Elemente hinzufügen, während Sie eine Schleife erstellen, um jedes Mal, wenn Sie hinzufügen, Ereignisse bei Änderungen auszulösen, wodurch die Benutzeroberfläche gerendert wird. Dies ist sehr schlecht für die Leistung. Sie möchten die Benutzeroberfläche am Ende nur einmal aktualisieren. Die Änderungsereignisse sind zu feinkörnig.
  • Änderungslistener werden sofort auf einen Setter ausgelöst, was ein Problem darstellt, da der Änderungslistener Daten weiter ändern kann, wodurch mehr Änderungsereignisse ausgelöst werden. Dies ist schlecht, da auf Ihrem Stapel möglicherweise mehrere Änderungsereignisse gleichzeitig auftreten. Angenommen, Sie haben zwei Arrays, die aus irgendeinem Grund synchron gehalten werden müssen. Sie können nur zu dem einen oder anderen hinzufügen, aber jedes Mal, wenn Sie hinzufügen, lösen Sie ein Änderungsereignis aus, das jetzt eine inkonsistente Sicht auf die Welt hat. Dies ist ein sehr ähnliches Problem wie das Sperren von Threads, das JavaScript vermeidet, da jeder Rückruf ausschließlich und vollständig ausgeführt wird. Änderungsereignisse brechen dies, da Setter weitreichende Konsequenzen haben können, die nicht beabsichtigt und nicht offensichtlich sind, was das Thread-Problem erneut verursacht. Es stellt sich heraus, dass Sie die Ausführung des Listeners verzögern und garantieren möchten:

Was ist mit Leistung?

Es scheint also, dass wir langsam sind, da Dirty-Checking ineffizient ist. Hier müssen wir uns reelle Zahlen ansehen und nicht nur theoretische Argumente haben, sondern zunächst einige Einschränkungen definieren.

Menschen sind:

  • Langsam - Alles, was schneller als 50 ms ist, ist für den Menschen nicht wahrnehmbar und kann daher als "sofort" betrachtet werden.

  • Eingeschränkt - Sie können einem Menschen nicht wirklich mehr als 2000 Informationen auf einer einzigen Seite anzeigen. Alles andere ist eine wirklich schlechte Benutzeroberfläche, und Menschen können dies sowieso nicht verarbeiten.

Die eigentliche Frage lautet also: Wie viele Vergleiche können Sie in 50 ms mit einem Browser durchführen? Diese Frage ist schwer zu beantworten, da viele Faktoren ins Spiel kommen. Hier ist jedoch ein Testfall: http://jsperf.com/angularjs-digest/6, mit dem 10.000 Beobachter erstellt werden. In einem modernen Browser dauert dies knapp 6 ms. In Internet Explorer 8 dauert es ungefähr 40 ms. Wie Sie sehen, ist dies heutzutage selbst bei langsamen Browsern kein Problem. Es gibt eine Einschränkung: Die Vergleiche müssen einfach sein, um in das Zeitlimit zu passen ... Leider ist es viel zu einfach, einen langsamen Vergleich in AngularJS einzufügen, sodass es einfach ist, langsame Anwendungen zu erstellen, wenn Sie nicht wissen, was Sie sind sind dabei. Wir hoffen jedoch auf eine Antwort, indem wir ein Instrumentierungsmodul bereitstellen, das Ihnen zeigt, welche Vergleiche langsam sind.

Es stellt sich heraus, dass Videospiele und GPUs den Dirty-Checking-Ansatz verwenden, insbesondere weil er konsistent ist. Solange sie die Bildwiederholfrequenz des Monitors überschreiten (normalerweise 50-60 Hz oder alle 16,6-20 ms), ist jede Leistung darüber eine Verschwendung. Sie sollten also besser mehr Material zeichnen, als die FPS zu erhöhen.

Misko Hevery
quelle
32
@Mark - Ja, in KO fügen Sie einfach .extend ({throttle: 500}) hinzu, um 500 Millisekunden nach dem letzten Änderungsereignis zu warten, bevor Sie darauf reagieren.
Daniel Earwicker
158
Diese ganze Antwort ist großartig, abgesehen von "Solange sie 50 fps erreichen, ist jede Leistung darüber eine Verschwendung, da das menschliche Auge sie nicht schätzen kann. Sie sollten also besser mehr Material zeichnen, als höhere fps zu erzielen." Diese Aussage ist je nach Anwendung völlig falsch. Das Auge kann definitiv mehr als 50 fps schätzen, und wie die verschiedenen Probleme mit VR zeigen (lesen Sie eines der neuesten von John Carmack oder Michael Abrash, insbesondere dessen GDC 2013 VR-Vortrag), sind 50 fps tatsächlich viel zu langsam. Davon abgesehen ist Ihre Antwort großartig. Ich möchte einfach nicht, dass sich Fehlinformationen verbreiten.
Nate Bundy
10
@ DavidRivers uns ist µs genau wie in utorrent 1µs = 0,000001s
Thorgeir
33
Die Aussage könnte leicht umgekehrt gesagt werden als "Dirty Checking ist eine clevere Funktion für ein Problem, das Knockout nicht hat". ES6 verwendet Observable und Angular beseitigt schmutzige Überprüfungen. Die reale Welt hat diese Antwort eingeholt und gezeigt, dass sie falsch ist.
konisch
17
"Alles, was schneller als 50 ms ist, ist für den Menschen nicht wahrnehmbar" ist nicht wahr. Beim Testen haben wir festgestellt, dass unsere Kunden leicht zwischen einer Aktualisierungslatenz von 50 ms (20 fps) und einer Aktualisierungslatenz von 16,6 ms (60 fps) unterscheiden können. Szenen, die mit der früheren Geschwindigkeit laufen, werden durchweg schlechter bewertet, "wie hat es sich angefühlt", selbst wenn die Framerate nicht bewusst registriert wurde.
Crashworks
323

Misko hat bereits eine hervorragende Beschreibung der Funktionsweise der Datenbindungen gegeben, aber ich möchte meine Meinung zum Leistungsproblem bei der Datenbindung hinzufügen.

Wie Misko feststellte, treten bei etwa 2000 Bindungen Probleme auf, aber Sie sollten ohnehin nicht mehr als 2000 Informationen auf einer Seite haben. Dies mag zutreffen, aber nicht jede Datenbindung ist für den Benutzer sichtbar. Sobald Sie mit dem Erstellen eines Widgets oder Datenrasters mit bidirektionaler Bindung beginnen, können Sie problemlos 2000 Bindungen treffen, ohne eine schlechte UX zu haben.

Stellen Sie sich beispielsweise ein Kombinationsfeld vor, in das Sie Text eingeben können, um die verfügbaren Optionen zu filtern. Diese Art der Steuerung könnte ~ 150 Elemente haben und dennoch sehr gut verwendbar sein. Wenn es eine zusätzliche Funktion hat (zum Beispiel eine bestimmte Klasse für die aktuell ausgewählte Option), erhalten Sie 3-5 Bindungen pro Option. Wenn Sie drei dieser Widgets auf eine Seite setzen (z. B. eines zur Auswahl eines Landes, das andere zur Auswahl einer Stadt in diesem Land und das dritte zur Auswahl eines Hotels), befinden Sie sich bereits zwischen 1000 und 2000 Bindungen.

Oder betrachten Sie ein Datenraster in einer Unternehmenswebanwendung. 50 Zeilen pro Seite sind nicht unangemessen, von denen jede 10 bis 20 Spalten haben kann. Wenn Sie dies mit ng-Wiederholungen erstellen und / oder Informationen in einigen Zellen haben, die einige Bindungen verwenden, können Sie sich allein mit diesem Raster 2000 Bindungen nähern.

Ich finde, dass dies ein großes Problem bei der Arbeit mit AngularJS ist, und die einzige Lösung, die ich bisher finden konnte, besteht darin, Widgets ohne bidirektionale Bindung zu erstellen, anstatt ngOnce zu verwenden, Beobachter und ähnliche Tricks abzumelden oder zu konstruieren Anweisungen, die das DOM mit jQuery- und DOM-Manipulation erstellen. Ich bin der Meinung, dass dies den Zweck der Verwendung von Angular in erster Linie zunichte macht.

Ich würde gerne Vorschläge hören, wie ich damit umgehen kann, aber dann sollte ich vielleicht meine eigene Frage schreiben. Ich wollte dies in einen Kommentar einfügen, aber es stellte sich heraus, dass es viel zu lang dafür war ...

TL; DR
Die Datenbindung kann auf komplexen Seiten zu Leistungsproblemen führen.

MW.
quelle
26
Ja, ich stimme dem zu. Die Hauptverantwortung unserer App besteht darin, Verbindungen zwischen verschiedenen Entitäten anzuzeigen. Eine bestimmte Seite kann 10 Abschnitte enthalten. Jeder Abschnitt hat eine Tabelle. Jede Tabelle verfügt über 2-5 Typeahead-Filter. Jede Tabelle hat 2-5 Spalten mit jeweils 10 Zeilen. Sehr schnell stoßen wir auf Perf-Probleme und gehen mit den "ähnlichen Tricks" -Optionen um.
Scott Silvi
10
Ist es fair zu sagen, dass es bei Angular nicht nur um Datenbindung geht und einige Apps diese Funktion möglicherweise nicht genau aus den von anderen genannten Gründen verwenden möchten? Ich denke, der Ansatz von DI und Modularität ist selbst viel wert; Magische automatische Bindung ist nett, aber in jeder vorhandenen Implementierung gibt es Kompromisse bei der Leistung. Angulars Weg ist für die meisten CRUD-Web-Apps wohl überlegen, und die Leute stoßen nur an eine Wand, indem sie versuchen, sie auf die Spitze zu treiben. Es wäre schön, wenn eine alternative Methode zum Abhören von Ereignissen unterstützt würde, aber vielleicht ist das für ein einzelnes Framework grundlegend zu komplex?
Jason Boyd
8
Angular verfügt jetzt über eine einmalige und einmalige Datenbindung, um dieses Problem zu beheben. Darüber hinaus verfügt es jetzt über Indizes für Ihre Repeater-Quelle, mit denen Sie die Liste ändern können, ohne den Dom für den gesamten Inhalt neu zu erstellen.
Gaute Løken
6
@ MW. Ehrlich gesagt dachte ich, Bind-Once sei im Kern. Aber es scheint nicht so. Es ist nur etwas, was Sie tun können, wenn Sie Ihre eigenen Anweisungen schreiben und Dinge verknüpfen, ohne sie anzusehen. Es gibt jedoch einen UX-Mod dafür: github.com/pasvaz/bindonce
Gaute Løken
9
Ein Ruf aus der Zukunft für alle, die dies lesen: Einmaliges
Nobita
158

Durch schmutziges Überprüfen des $scopeObjekts

Angular verwaltet eine einfache arrayAnzahl von Beobachtern in den $scopeObjekten. Wenn Sie eine inspizieren, werden $scopeSie feststellen, dass sie einen arrayangerufenen enthält $$watchers.

Jeder Beobachter ist ein object, der unter anderem enthält

  1. Ein Ausdruck, den der Beobachter überwacht. Dies könnte nur ein attributeName oder etwas Komplizierteres sein.
  2. Ein letzter bekannter Wert des Ausdrucks. Dies kann mit dem aktuell berechneten Wert des Ausdrucks verglichen werden. Wenn sich die Werte unterscheiden, löst der Beobachter die Funktion aus und markiert sie $scopeals verschmutzt.
  3. Eine Funktion, die ausgeführt wird, wenn der Watcher verschmutzt ist.

Wie Beobachter definiert werden

Es gibt viele verschiedene Möglichkeiten, einen Beobachter in AngularJS zu definieren.

  • Sie können explizit $watchein attributeein $scope.

    $scope.$watch('person.username', validateUnique);
  • Sie können eine {{}}Interpolation in Ihre Vorlage einfügen (ein Beobachter wird aktuell für Sie erstellt $scope).

    <p>username: {{person.username}}</p>
  • Sie können eine Anweisung ng-modelanfordern, um den Beobachter für Sie zu definieren.

    <input ng-model="person.username" />

Das $digest Zyklus prüft alle Beobachter auf ihren letzten Wert

Wenn wir mit AngularJS über die normalen Kanäle (ng-model, ng-repeat usw.) interagieren, wird durch die Direktive ein Digest-Zyklus ausgelöst.

Ein Verdauungszyklus ist eine Tiefenüberquerung $scopealler seiner Kinder . Für jeden $scope objectiterieren wir über seine $$watchers arrayund bewerten alle Ausdrücke. Wenn sich der neue Ausdruckswert vom letzten bekannten Wert unterscheidet, wird die Watcher-Funktion aufgerufen. Diese Funktion kann einen Teil des DOM neu kompilieren, einen Wert für neu berechnen $scope, einen auslösen AJAX request, alles, was Sie dazu benötigen.

Jeder Bereich wird durchlaufen und jeder Überwachungsausdruck wird ausgewertet und mit dem letzten Wert verglichen.

Wenn ein Beobachter ausgelöst wird, $scopeist der verschmutzt

Wenn ein Beobachter ausgelöst wird, weiß die App, dass sich etwas geändert hat, und das $scopewird als verschmutzt markiert.

Watcher-Funktionen können andere Attribute auf $scopeoder auf einem übergeordneten Element ändern $scope. Wenn eine $watcherFunktion ausgelöst wurde, können wir nicht garantieren, dass unsere andere$scope noch sauber sind, und führen daher den gesamten Digest-Zyklus erneut aus.

Dies liegt daran, dass AngularJS über eine bidirektionale Bindung verfügt, sodass Daten wieder in den $scopeBaum übertragen werden können. Wir können einen Wert auf einem höheren Wert ändern $scope, der bereits verdaut wurde. Vielleicht ändern wir einen Wert auf der $rootScope.

Wenn das $digestschmutzig ist, führen wir das gesamte aus$digest Zyklus erneut aus

Wir durchlaufen den $digestZyklus kontinuierlich, bis entweder der Verdauungszyklus sauber ist (alles$watch Ausdrücke haben den gleichen Wert wie im vorherigen Zyklus) oder wir das Digest-Limit erreichen. Standardmäßig ist diese Grenze auf 10 festgelegt.

Wenn wir das Digest-Limit erreichen, wird AngularJS einen Fehler in der Konsole auslösen:

10 $digest() iterations reached. Aborting!

Der Digest ist hart für die Maschine, aber für den Entwickler einfach

Wie Sie sehen können, überprüft AngularJS jedes Mal, wenn sich in einer AngularJS-App etwas ändert, jeden einzelnen Beobachter in der $scopeHierarchie, um zu sehen, wie er reagieren soll. Für einen Entwickler ist dies ein enormer Produktivitätsvorteil, da Sie jetzt fast keinen Verkabelungscode mehr schreiben müssen. AngularJS merkt nur, wenn sich ein Wert geändert hat, und macht den Rest der App mit der Änderung konsistent.

Aus Sicht der Maschine ist dies jedoch äußerst ineffizient und verlangsamt unsere App, wenn wir zu viele Beobachter erstellen. Misko hat eine Zahl von ungefähr 4000 Beobachtern angegeben, bevor sich Ihre App in älteren Browsern langsam anfühlt.

Diese Grenze ist leicht zu erreichen, wenn Sie ng-repeatüber eine großeJSON array zum Beispiel . Sie können dem entgegenwirken, indem Sie Funktionen wie die einmalige Bindung zum Kompilieren einer Vorlage verwenden, ohne Beobachter zu erstellen.

So vermeiden Sie, dass zu viele Beobachter erstellt werden

Jedes Mal, wenn Ihr Benutzer mit Ihrer App interagiert, wird jeder einzelne Beobachter in Ihrer App mindestens einmal bewertet. Ein großer Teil der Optimierung einer AngularJS-App besteht darin, die Anzahl der Beobachter in Ihrem $scopeBaum zu reduzieren . Eine einfache Möglichkeit, dies zu tun, ist die einmalige Bindung .

Wenn Sie Daten haben, die sich selten ändern, können Sie sie nur einmal mit der :: -Syntax binden, wie folgt:

<p>{{::person.username}}</p>

oder

<p ng-bind="::person.username"></p>

Die Bindung wird nur ausgelöst, wenn die enthaltene Vorlage gerendert und die Daten geladen wurden $scope.

Dies ist besonders wichtig, wenn Sie eine ng-repeatmit vielen Artikeln haben.

<div ng-repeat="person in people track by username">
  {{::person.username}}
</div>
überleuchtet
quelle
Danke @ user2864740 - obwohl es richtig ist, dass Miskos Antwort oben sein sollte. Er kennt das Framework besser als jeder andere und es ist ziemlich cool, dass er sich mit Stack Overflow beschäftigt.
Superluminary
4
Ich bin nicht der Meinung, dass diese Antwort ganz oben stehen sollte. Es gibt einen Unterschied zwischen dem Wissen um etwas und dem Schreiben einer relevanten / detaillierten Antwort auf eine bestimmte Frage. Es gibt bessere Möglichkeiten, Auszeichnungen zu erhalten. Wie auch immer ..
user2864740
1
Ich bezweifle nicht, dass dies wahr ist, aber Fragen, Fragen und Antworten, Antworten :)
user2864740
3
Eine nette Antwort darüber, wie sich der Dirty-Check verhält und wie er tatsächlich bewertet wird. Eines war in Miskos Antwort nicht allzu klar.
Strider
3
Hervorragende und detaillierte Antwort. @superluminary, danke für diese Antwort. Darüber hinaus komme ich nach dem Lesen dieser Antwort zu dem Punkt, dass wir keinen nicht-idempotenten Ausdruck als einen beobachteten Ausdruck hinzufügen dürfen.
Mangu Singh Rajpurohit
81

Das ist mein Grundverständnis. Es kann durchaus falsch sein!

  1. Elemente werden überwacht, indem eine Funktion (Rückgabe des zu überwachenden Objekts) an das übergeben wird $watch Methode übergeben wird.
  2. Änderungen an überwachten Elementen müssen innerhalb eines von der $applyMethode umschlossenen Codeblocks vorgenommen werden.
  3. Am Ende $applyder $digestMethode wird die Methode aufgerufen, die jede der Uhren durchläuft und prüft, ob sie sich seit dem letzten Ausführen geändert haben $digest.
  4. Wenn Änderungen gefunden werden, wird der Digest erneut aufgerufen, bis sich alle Änderungen stabilisiert haben.

In der normalen Entwicklung weist die Datenbindungssyntax im HTML den AngularJS-Compiler an, die Uhren für Sie zu erstellen, und die Controller-Methoden werden $applybereits ausgeführt. Für den Anwendungsentwickler ist also alles transparent.

Pete BD
quelle
4
Wann wird die Apply-Methode ausgelöst?
Numan Salati
3
@EliseuMonar Die Digest-Schleife wird aufgrund eines Ereignisses oder eines Aufrufs von $ apply () ausgeführt. Sie wird nicht regelmäßig basierend auf einem Timer aufgerufen. siehe Wie funktioniert die $ watch-Funktion von AngularJS? und wie funktioniert das Binden und Verdauen in AngularJS?
adl
1
@remi, ich bin nicht besorgt über die letzte Version von AngularJS. Verwenden sie bereits Proxys oder Object.observe? Wenn nicht, befinden sie sich noch in der Dirty-Checking-Ära, in der eine Zeitschleife erstellt wird, um festzustellen, ob sich die Modellattribute geändert haben.
Eliseu Monar dos Santos
1
Ich habe gelesen, dass Digest maximal zehnmal ausgeführt wird. sitepoint.com/understanding-angulars-apply-digest
user137717
62

Ich habe mich das eine Weile selbst gefragt. Wie ändert sich ohne Setter AngularJSeine Änderung am $scopeObjekt? Fragt es sie ab?

Was es tatsächlich tut, ist Folgendes: Jeder "normale" Ort, an dem Sie das Modell ändern, wurde bereits aus dem Bauch heraus aufgerufen AngularJS, sodass es automatisch $applynach Ihnen ruft , nachdem Ihr Code ausgeführt wurde. Angenommen, Ihr Controller verfügt über eine Methode, die ng-clickan ein Element angeschlossen ist. Da AngularJSder Aufruf dieser Methode für Sie zusammengeführt wird, besteht die Möglichkeit, eine $applyan der entsprechenden Stelle durchzuführen. Ebenso werden Ausdrücke, die direkt in den Ansichten angezeigt werden, von ausgeführt, AngularJSso dass dies der Fall ist$apply .

Wenn in der Dokumentation davon gesprochen wird, dass $applyCode außerhalb vonAngularJS manuell aufgerufen werden muss , handelt es sich um Code, der beim Ausführen nicht von AngularJSselbst im Aufrufstapel stammt.

jpsimons
quelle
32

Mit Bildern erklären:

Die Datenbindung erfordert eine Zuordnung

Die Referenz im Bereich ist nicht genau die Referenz in der Vorlage. Wenn Sie zwei Objekte mit Daten binden, benötigen Sie ein drittes, das das erste abhört und das andere ändert.

Geben Sie hier die Bildbeschreibung ein

Wenn Sie das ändern <input>, berühren Sie hier die Datenreferenz3 . Und die klassische Daten-bind mecanism ändert Daten ref4 . Wie {{data}}bewegen sich die anderen Ausdrücke?

Ereignisse führen zu $ ​​Digest ()

Geben Sie hier die Bildbeschreibung ein

Angular behält eine oldValueund newValuevon jeder Bindung bei. Und nach jedem Angular-Ereignis$digest() überprüft die berühmte Schleife die WatchList, um festzustellen, ob sich etwas geändert hat. Diese Winkel Ereignisse sind ng-click, ng-change, $httpabgeschlossen ... Die $digest()Schleife wird solange irgendwelchen oldValueunterscheidet sich von dernewValue .

Im vorherigen Bild wird festgestellt, dass sich Datenreferenz1 und Datenreferenz2 geändert haben.

Schlussfolgerungen

Es ist ein bisschen wie das Ei und Huhn. Man weiß nie, wer anfängt, aber hoffentlich funktioniert es die meiste Zeit wie erwartet.

Der andere Punkt ist, dass Sie die Auswirkungen einer einfachen Bindung auf den Speicher und die CPU leicht verstehen können. Hoffentlich sind Desktops fett genug, um damit umzugehen. Handys sind nicht so stark.

Nicolas Zozol
quelle
22

Offensichtlich gibt es keine regelmäßige Überprüfung, Scopeob sich Änderungen an den damit verbundenen Objekten ergeben. Nicht alle an den Bereich angehängten Objekte werden überwacht. Scope unterhält prototypisch einen $$ Beobachter . Scopedurchläuft dies nur, $$watcherswenn $digestes aufgerufen wird.

Angular fügt den $$ -Betrachtern für jeden dieser einen Beobachter hinzu

  1. {{expression}} - In Ihren Vorlagen (und überall dort, wo es einen Ausdruck gibt) oder wenn wir ng-model definieren.
  2. $ scope. $ watch ('Ausdruck / Funktion') - In Ihrem JavaScript können wir einfach ein Bereichsobjekt anhängen, das eckig überwacht werden soll.

Die $ watch- Funktion akzeptiert drei Parameter:

  1. Die erste ist eine Überwachungsfunktion, die nur das Objekt zurückgibt, oder wir können einfach einen Ausdruck hinzufügen.

  2. Die zweite ist eine Listener-Funktion, die aufgerufen wird, wenn sich das Objekt ändert. Alle Dinge wie DOM-Änderungen werden in dieser Funktion implementiert.

  3. Der dritte ist ein optionaler Parameter, der einen Booleschen Wert annimmt. Wenn sein wahrer Winkel eckig ist, beobachtet er das Objekt und wenn sein falscher Winkel nur eine Referenz, die das Objekt beobachtet. Die grobe Implementierung von $ watch sieht so aus

Scope.prototype.$watch = function(watchFn, listenerFn) {
   var watcher = {
       watchFn: watchFn,
       listenerFn: listenerFn || function() { },
       last: initWatchVal  // initWatchVal is typically undefined
   };
   this.$$watchers.push(watcher); // pushing the Watcher Object to Watchers  
};

In Angular gibt es eine interessante Sache namens Digest Cycle. Der $ Digest-Zyklus beginnt als Ergebnis eines Aufrufs von $ scope. $ Digest (). Angenommen, Sie ändern ein $ scope-Modell in einer Handlerfunktion über die Direktive ng-click. In diesem Fall löst AngularJS automatisch einen $ Digest-Zyklus aus, indem $ Digest () aufgerufen wird. Zusätzlich zu ng-click gibt es mehrere andere integrierte Anweisungen / Dienste, mit denen Sie Modelle ändern können (z. B. ng-model, $ timeout usw.). und automatisch einen $ Digest-Zyklus auslösen. Die grobe Implementierung von $ Digest sieht so aus.

Scope.prototype.$digest = function() {
      var dirty;
      do {
          dirty = this.$$digestOnce();
      } while (dirty);
}
Scope.prototype.$$digestOnce = function() {
   var self = this;
   var newValue, oldValue, dirty;
   _.forEach(this.$$watchers, function(watcher) {
          newValue = watcher.watchFn(self);
          oldValue = watcher.last;   // It just remembers the last value for dirty checking
          if (newValue !== oldValue) { //Dirty checking of References 
   // For Deep checking the object , code of Value     
   // based checking of Object should be implemented here
             watcher.last = newValue;
             watcher.listenerFn(newValue,
                  (oldValue === initWatchVal ? newValue : oldValue),
                   self);
          dirty = true;
          }
     });
   return dirty;
 };

Wenn wir die Funktion setTimeout () von JavaScript verwenden, um ein Bereichsmodell zu aktualisieren, kann Angular nicht wissen, was Sie möglicherweise ändern. In diesem Fall liegt es in unserer Verantwortung, $ apply () manuell aufzurufen, wodurch ein $ Digest-Zyklus ausgelöst wird. Wenn Sie eine Direktive haben, die einen DOM-Ereignis-Listener einrichtet und einige Modelle innerhalb der Handler-Funktion ändert, müssen Sie $ apply () aufrufen, um sicherzustellen, dass die Änderungen wirksam werden. Die große Idee von $ apply ist, dass wir Code ausführen können, der Angular nicht kennt. Dieser Code kann möglicherweise noch Änderungen am Gültigkeitsbereich vornehmen. Wenn wir diesen Code in $ apply einschließen, wird $ digest () aufgerufen. Grobe Implementierung von $ apply ().

Scope.prototype.$apply = function(expr) {
       try {
         return this.$eval(expr); //Evaluating code in the context of Scope
       } finally {
         this.$digest();
       }
};
Sasank Sunkavalli
quelle
15

AngularJS verarbeitet den Datenbindungsmechanismus mithilfe von drei leistungsstarken Funktionen: $ watch () , $ Digest () und $ apply () . Meistens ruft AngularJS die Bereiche $ scope. $ Watch () und $ scope. $ Digest () auf. In einigen Fällen müssen Sie diese Funktionen jedoch möglicherweise manuell aufrufen, um sie mit neuen Werten zu aktualisieren.

$ watch () : -

Diese Funktion wird verwendet, um Änderungen in einer Variablen im Bereich $ zu beobachten. Es werden drei Parameter akzeptiert: Ausdruck, Listener und Gleichheitsobjekt, wobei Listener und Gleichheitsobjekt optionale Parameter sind.

$ Digest () -

Diese Funktion durchläuft alle Uhren im $ scope-Objekt und seine untergeordneten $ scope-Objekte
(falls vorhanden). Wenn $ Digest () über die Uhren iteriert, wird überprüft, ob sich der Wert des Ausdrucks geändert hat. Wenn sich der Wert geändert hat, ruft AngularJS den Listener mit neuem und altem Wert auf. Die Funktion $ Digest () wird immer dann aufgerufen, wenn AngularJS dies für erforderlich hält. Zum Beispiel nach einem Klick auf eine Schaltfläche oder nach einem AJAX-Aufruf. In einigen Fällen ruft AngularJS die Funktion $ digest () nicht für Sie auf. In diesem Fall müssen Sie es selbst nennen.

$ apply () -

Angular aktualisiert automatisch nur die Modelländerungen, die sich im AngularJS-Kontext befinden. Wenn Sie in einem Modell außerhalb des Angular-Kontexts Änderungen vornehmen (z. B. Browser-DOM-Ereignisse, setTimeout, XHR oder Bibliotheken von Drittanbietern), müssen Sie Angular über die Änderungen informieren, indem Sie $ apply () manuell aufrufen. Wenn der Funktionsaufruf $ apply () beendet ist, ruft AngularJS $ Digest () intern auf, sodass alle Datenbindungen aktualisiert werden.

Bharath Kumar
quelle
7

Es kam vor, dass ich ein Datenmodell einer Person mit einem Formular verknüpfen musste. Ich habe eine direkte Zuordnung der Daten zum Formular vorgenommen.

Zum Beispiel, wenn das Modell so etwas wie:

$scope.model.people.name

Die Steuereingabe des Formulars:

<input type="text" name="namePeople" model="model.people.name">

Auf diese Weise wird dies automatisch in der Ansicht angezeigt, wenn Sie den Wert des Objektcontrollers ändern.

Ein Beispiel, bei dem ich das Modell übergeben habe, wird aus Serverdaten aktualisiert, wenn Sie nach einer Postleitzahl und einer Postleitzahl fragen, die auf schriftlichen Ladevorgängen basieren, eine Liste der dieser Ansicht zugeordneten Kolonien und Städte erstellen und standardmäßig den ersten Wert mit dem Benutzer festlegen. Und das habe ich sehr gut gemacht. Was passiert, ist, dass es angularJSmanchmal einige Sekunden dauert, um das Modell zu aktualisieren. Dazu können Sie einen Spinner einsetzen, während Sie die Daten anzeigen.

gartox
quelle
14
Ich habe diese Antwort 5 Mal gelesen und verstehe immer noch nicht, was hier gemeint ist.
Sbedulin
1
Die Antwort scheint mir ein Rätsel zu sein
Aman
6
  1. Die Einweg-Datenbindung ist ein Ansatz, bei dem ein Wert aus dem Datenmodell entnommen und in ein HTML-Element eingefügt wird. Es gibt keine Möglichkeit, das Modell aus der Ansicht zu aktualisieren. Es wird in klassischen Vorlagensystemen verwendet. Diese Systeme binden Daten nur in eine Richtung.

  2. Die Datenbindung in Angular-Apps ist die automatische Synchronisierung von Daten zwischen den Modell- und Ansichtskomponenten.

Mit der Datenbindung können Sie das Modell als die einzige Quelle der Wahrheit in Ihrer Anwendung behandeln. Die Ansicht ist jederzeit eine Projektion des Modells. Wenn das Modell geändert wird, spiegelt die Ansicht die Änderung wider und umgekehrt.

Shankar Gangadhar
quelle
5

Hier ist ein Beispiel für die Datenbindung mit AngularJS unter Verwendung eines Eingabefelds. Ich werde es später erklären

HTML Quelltext

<div ng-app="myApp" ng-controller="myCtrl" class="formInput">
     <input type="text" ng-model="watchInput" Placeholder="type something"/>
     <p>{{watchInput}}</p> 
</div>

AngularJS-Code

myApp = angular.module ("myApp", []);
myApp.controller("myCtrl", ["$scope", function($scope){
  //Your Controller code goes here
}]);

Wie Sie im obigen Beispiel sehen können, AngularJS Anwendungen ng-modelzu hören und sehen , was auf HTML - Elementen geschehen, vor allem auf inputFelder. Wenn etwas passiert, tun Sie etwas. In unserem Fall ng-modelist an unsere Ansicht gebunden, unter Verwendung der Schnurrbartnotation{{}} . Was auch immer in das Eingabefeld eingegeben wird, wird sofort auf dem Bildschirm angezeigt. Und das ist das Schöne an der Datenbindung, wenn AngularJS in seiner einfachsten Form verwendet wird.

Hoffe das hilft.

Ein Arbeitsbeispiel finden Sie hier auf Codepen

AllJs
quelle
5

AngularJs unterstützt die bidirektionale Datenbindung .
Dies bedeutet, dass Sie auf die Datenansicht -> Controller & Controller -> Ansicht zugreifen können

Zum Beispiel.

1)

// If $scope have some value in Controller. 
$scope.name = "Peter";

// HTML
<div> {{ name }} </div>

O / P.

Peter

Sie können Daten in ng-modelLike binden : -
2)

<input ng-model="name" />

<div> {{ name }} </div>

Hier im obigen Beispiel wird die Eingabe des Benutzers im <div>Tag angezeigt.

Wenn Sie Eingaben von HTML an den Controller binden möchten: -
3)

<form name="myForm" ng-submit="registration()">
   <label> Name </lbel>
   <input ng-model="name" />
</form>

Wenn Sie hier einen Eingang namein der Steuerung verwenden möchten ,

$scope.name = {};

$scope.registration = function() {
   console.log("You will get the name here ", $scope.name);
};

ng-modelbindet unsere Sichtweise und macht sie zum Ausdruck {{ }}.
ng-modelsind die Daten, die dem Benutzer in der Ansicht angezeigt werden und mit denen der Benutzer interagiert.
So ist es einfach, Daten in AngularJs zu binden.

ojus kulkarni
quelle
4

Angular.js erstellt einen Watcher für jedes Modell, das wir in der Ansicht erstellen. Jedes Mal, wenn ein Modell geändert wird, wird eine "ng-schmutzige" Klasse an das Modell angehängt, sodass der Beobachter alle Modelle mit der Klasse "ng-schmutzig" beobachtet und ihre Werte in der Steuerung aktualisiert und umgekehrt.

Shankar Gangadhar
quelle
3

Datenbindung:

Was ist Datenbindung?

Immer wenn der Benutzer die Daten in der Ansicht ändert, erfolgt eine Aktualisierung dieser Änderung im Bereichsmodell und umgekehrt.

Wie ist es möglich?

Kurze Antwort: Mit Hilfe des Verdauungszyklus.

Beschreibung: Angular js setzt den Watcher auf das Scope-Modell, wodurch die Listener-Funktion ausgelöst wird, wenn sich das Modell ändert.

$scope.$watch('modelVar' , function(newValue,oldValue){

// Dom Update Code mit neuem Wert

});

Wann und wie wird die Watcher-Funktion aufgerufen?

Die Watcher-Funktion wird als Teil des Digest-Zyklus aufgerufen.

Der Digest-Zyklus wird automatisch als Teil von eckigen js aufgerufen, die in Direktiven / Diensten wie ng-model, ng-bind, $ timeout, ng-click und anderen integriert sind, mit denen Sie den Digest-Zyklus auslösen können.

Verdauungszyklusfunktion:

$scope.$digest() -> digest cycle against the current scope.
$scope.$apply() -> digest cycle against the parent scope 

dh$rootScope.$apply()

Hinweis: $ apply () ist gleich $ rootScope. $ Digest () bedeutet, dass die Dirty-Prüfung direkt vom Stamm oder oben oder vom übergeordneten Bereich bis zu allen untergeordneten $ Bereichen in der eckigen js-Anwendung beginnt.

Die oben genannten Funktionen funktionieren im IE des Browsers für die genannten Versionen auch nur, indem Sie sicherstellen, dass es sich bei Ihrer Anwendung um eine eckige js-Anwendung handelt. Dies bedeutet, dass Sie die im script-Tag angegebene Angularjs-Framework-Skriptdatei verwenden.

Vielen Dank.

Dhana
quelle