Wie ignoriere ich das anfängliche Laden, wenn ich Modelländerungen in AngularJS beobachte?

183

Ich habe eine Webseite, die als Editor für eine einzelne Entität dient und als tiefes Diagramm in der Eigenschaft $ scope.fieldcontainer gespeichert ist. Nachdem ich eine Antwort von meiner REST-API erhalten habe (über $ resource), füge ich 'fieldcontainer' eine Überwachung hinzu. Ich benutze diese Uhr, um festzustellen, ob die Seite / Entität "schmutzig" ist. Im Moment lasse ich die Schaltfläche "Speichern" springen, aber ich möchte die Schaltfläche "Speichern" wirklich unsichtbar machen, bis der Benutzer das Modell verschmutzt.

Was ich bekomme, ist ein einzelner Auslöser der Uhr, der meiner Meinung nach geschieht, weil die Zuweisung von .fieldcontainer = ... unmittelbar nach dem Erstellen meiner Uhr erfolgt. Ich habe darüber nachgedacht, nur eine "dirtyCount" -Eigenschaft zu verwenden, um den anfänglichen Fehlalarm zu absorbieren, aber das fühlt sich sehr hackig an ... und ich dachte, es muss eine "Angular Idiomatic" -Methode geben, um damit umzugehen - ich bin nicht der einzige Verwenden einer Uhr, um ein schmutziges Modell zu erkennen.

Hier ist der Code, mit dem ich meine Uhr eingestellt habe:

 $scope.fieldcontainer = Message.get({id: $scope.entityId },
            function(message,headers) {
                $scope.$watch('fieldcontainer',
                    function() {
                        console.log("model is dirty.");
                        if ($scope.visibility.saveButton) {
                            $('#saveMessageButtonRow').effect("bounce", { times:5, direction: 'right' }, 300);
                        }
                    }, true);
            });

Ich denke immer nur, dass es einen saubereren Weg geben muss, als meinen "UI Dirtyying" -Code mit einem "if (dirtyCount> 0)" zu schützen ...

Kevin Hoffman
quelle
Ich muss auch in der Lage sein, $ scope.fieldcontainer basierend auf bestimmten Schaltflächen neu zu laden (z. B. Laden einer früheren Version der Entität). Dazu müsste ich die Uhr anhalten, neu laden und dann die Uhr wieder aufnehmen.
Kevin Hoffman
Würden Sie in Betracht ziehen, meine Antwort als akzeptierte Antwort zu ändern? Diejenigen, die sich die Zeit nehmen, bis zum Ende zu lesen, sind sich einig, dass dies die richtige Lösung ist.
Trixtur
@trixtur Ich habe das gleiche OP-Problem, aber in meinem Fall funktioniert Ihre Antwort nicht, weil mein alter Wert nicht funktioniert undefined. Es hat einen Standardwert, der für den Fall erforderlich ist, dass bei meinem Modellupdate nicht alle Informationen angezeigt werden. Einige Werte ändern sich also nicht, sondern müssen ausgelöst werden.
Oliholz
@oliholz ​​Anstatt also gegen undefiniert zu prüfen, nehmen Sie den "typeof" heraus und überprüfen Sie den old_fieldcontainer mit Ihrem Standardwert.
Trixtur
@ KevinHoffman Sie sollten die Antwort von MW bei der richtigen Antwort für andere Personen markieren, die diese Frage finden. Es ist eine viel bessere Lösung. Vielen Dank!
Dev01

Antworten:

117

Setzen Sie kurz vor dem ersten Laden ein Flag.

var initializing = true

und dann, wenn die erste $ watch feuert, tun Sie es

$scope.$watch('fieldcontainer', function() {
  if (initializing) {
    $timeout(function() { initializing = false; });
  } else {
    // do whatever you were going to do
  }
});

Das Flag wird erst am Ende des aktuellen Digest-Zyklus abgerissen, sodass die nächste Änderung nicht blockiert wird.

umgeschrieben
quelle
17
Ja, das wird funktionieren, aber der Vergleich des von @MW vorgeschlagenen alten und neuen Wertansatzes. ist sowohl einfacher als auch eckiger idiomatisch. Können Sie die akzeptierte Antwort aktualisieren?
Gerryster
2
Es wurde speziell hinzugefügt, um den alten Wert beim ersten Feuer immer auf den neuen Wert zu setzen, um zu vermeiden, dass dieser Flaggenhack ausgeführt werden muss
Charlie Martin,
2
Die Antwort von MW ist tatsächlich falsch, da der neue Wert auf den alten Wert den Fall nicht behandelt, in dem der alte Wert undefiniert ist.
Trixtur
1
Für Kommentatoren: Dies ist kein Gottmodell, nichts hindert Sie daran, die 'initialisierte' Variable in den Bereich eines Dekorateurs zu setzen und dann Ihre "normale" Uhr damit zu dekorieren. In jedem Fall liegt das völlig außerhalb des Rahmens dieser Frage.
umgeschrieben
1
Siehe plnkr.co/edit/VrWrHHWwukqnxaEM3J5i zum Vergleich der vorgeschlagenen Methoden, sowohl zum Einrichten von Watchern im Rückruf .get () als auch zur Bereichsinitialisierung.
umgeschrieben
482

Beim ersten Aufruf des Listeners sind der alte und der neue Wert identisch. Also mach einfach das:

$scope.$watch('fieldcontainer', function(newValue, oldValue) {
  if (newValue !== oldValue) {
    // do whatever you were going to do
  }
});

Dies ist eigentlich die Art und Weise, wie die Angular-Dokumente empfehlen, damit umzugehen :

Nachdem ein Beobachter im Bereich registriert wurde, wird der Listener fn asynchron (über $ evalAsync) aufgerufen, um den Beobachter zu initialisieren. In seltenen Fällen ist dies unerwünscht, da der Listener aufgerufen wird, wenn sich das Ergebnis von watchExpression nicht geändert hat. Um dieses Szenario im Listener fn zu erkennen, können Sie newVal und oldVal vergleichen. Wenn diese beiden Werte identisch sind (===), wurde der Listener aufgrund der Initialisierung aufgerufen

MW.
quelle
3
Dieser Weg ist idiomatischer als die Antwort von @
rewritten
25
Das ist nicht richtig. Der Punkt ist, die Änderung vom nullursprünglich geladenen Wert zu ignorieren, die Änderung nicht zu ignorieren, wenn keine Änderung vorliegt.
umgeschrieben am
1
Nun, die Listener-Funktion wird immer ausgelöst, wenn die Uhr erstellt wird. Dies ignoriert diesen ersten Anruf, was meiner Meinung nach der Fragesteller wollte. Wenn er die Änderung ausdrücklich ignorieren möchte, wenn der alte Wert null war, kann er natürlich oldValue mit null vergleichen ... aber ich glaube nicht, dass er das tun wollte.
MW.
Das OP fragt speziell nach Beobachtern für Ressourcen (von REST-Diensten). Zuerst werden Ressourcen erstellt, dann die Beobachter angewendet und dann die Attribute aus der Antwort geschrieben. Erst dann erstellt Angular einen Zyklus. Das Überspringen des ersten Zyklus mit einem "Initialisierungs" -Flag bietet eine Möglichkeit, einen Watcher nur auf initialisierte Ressourcen anzuwenden, aber dennoch nach Änderungen von / nach zu suchen null.
Umgeschrieben
7
Dies ist falsch, oldValuewird beim ersten Start null sein.
Kevin C.
42

Mir ist klar, dass diese Frage beantwortet wurde, aber ich habe einen Vorschlag:

$scope.$watch('fieldcontainer', function (new_fieldcontainer, old_fieldcontainer) {
    if (typeof old_fieldcontainer === 'undefined') return;

    // Other code for handling changed object here.
});

Die Verwendung von Flags funktioniert, hat aber einen gewissen Code-Geruch , finden Sie nicht?

trixtur
quelle
1
Dies ist der richtige Weg. In Coffeescript ist es so einfach wie return unless oldValue?(? Ist der existenzielle Operator in CS).
Kevin C.
1
Requisiten zum Wortcode riechen! Schauen Sie sich auch Gott Objekt lol. zu gut.
Matthew Harwood
1
Obwohl dies nicht die akzeptierte Antwort ist, funktionierte mein Code beim Auffüllen eines Objekts aus einer API und ich möchte verschiedene Speicherzustände beobachten. Sehr schön.
Lucas Reppe Welander
1
Danke - das hat mir geholfen
Wand Maker
1
Das ist großartig, aber in meinem Fall hatte ich die Daten als leeres Array instanziiert, bevor ich den AJAX-Aufruf an meine API ausführte. Überprüfen Sie daher die Länge dieses Arrays anstelle des Typs. Aber diese Antwort zeigt definitiv zumindest die effektivste Methode.
Saborknight
4

Beim erstmaligen Laden der aktuellen Werte ist das alte Wertefeld undefiniert. Das folgende Beispiel hilft Ihnen also, anfängliche Ladungen auszuschließen.

$scope.$watch('fieldcontainer', 
  function(newValue, oldValue) {
    if (newValue && oldValue && newValue != oldValue) {
      // here what to do
    }
  }), true;
Tahsin Turkoz
quelle
Sie möchten wahrscheinlich! == verwenden, da nullund undefinedin dieser Situation übereinstimmen werden, oder ( '1' == 1etc)
Jamie Pate
Im Allgemeinen haben Sie recht. In diesem Fall können newValue und oldValue aufgrund der vorherigen Überprüfungen jedoch keine solchen Eckwerte sein. Beide Werte haben denselben Typ, auch weil sie zum selben Feld gehören. Danke dir.
Tahsin Turkoz
3

Validieren Sie einfach den Status des neuen Werts:

$scope.$watch('fieldcontainer',function(newVal) {
      if(angular.isDefined(newVal)){
          //Do something
      }
});
Luis Saraza
quelle