867 Stimmen

AngularJS : Verhinderung des Fehlers $digest already in progress beim Aufruf von $scope.$apply()

Ich finde, dass ich meine Seite zu meinem Bereich manuell mehr und mehr seit dem Aufbau einer Anwendung in Angular aktualisieren müssen.

Die einzige mir bekannte Möglichkeit, dies zu tun, ist der Aufruf $apply() aus dem Geltungsbereich meiner Kontrolleure und Weisungen. Das Problem dabei ist, dass es einen Fehler in der Konsole auslöst, der lautet :

Fehler: $digest bereits in Bearbeitung

Weiß jemand, wie man diesen Fehler vermeidet oder das Gleiche auf eine andere Weise erreicht?

682voto

betaorbust Punkte 7928

Aus einer kürzlichen Diskussion mit den Jungs von Angular zu genau diesem Thema: Aus Gründen der Zukunftssicherheit sollten Sie nicht $$phase

Wenn man nach dem "richtigen" Weg fragt, lautet die Antwort derzeit

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

Ich stieß vor kurzem auf diese beim Schreiben von Angular-Dienste, um die Facebook, Google und Twitter-APIs, die in unterschiedlichem Maße, haben Rückrufe in übergeben zu wickeln.

Hier ist ein Beispiel aus einem Dienst. (Der Kürze halber wurde der Rest des Dienstes - das Einrichten von Variablen, das Einfügen von $timeout usw. -- wurde weggelassen.)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

Beachten Sie, dass das Verzögerungsargument für $timeout optional ist und standardmäßig auf 0 gesetzt wird, wenn es nicht gesetzt wird ( $Zeitüberschreitung ruft auf. $browser.defer die standardmäßig 0, wenn keine Verzögerung eingestellt ist )

Ein wenig unintuitiv, aber das ist die Antwort von den Jungs, die Angular schreiben, also ist es gut genug für mich!

674voto

Lee Punkte 9392

Verwenden Sie dieses Muster nicht - Dadurch werden mehr Fehler verursacht als behoben. Auch wenn Sie denken, dass es etwas behoben hat, hat es das nicht.

Sie können prüfen, ob ein $digest bereits im Gange ist, indem Sie prüfen $scope.$$phase .

if(!$scope.$$phase) {
  //$digest or $apply
}

$scope.$$phase wird zurückgegeben "$digest" ou "$apply" wenn ein $digest ou $apply ist im Gange. Ich glaube, der Unterschied zwischen diesen Zuständen besteht darin, dass $digest verarbeitet die Uhren des aktuellen Bereichs und seiner Kinder, und $apply wird die Beobachter aller Bereiche verarbeiten.

Um auf @dnc253 zurückzukommen: Wenn Sie sich dabei ertappen, dass Sie $digest ou $apply häufig, können Sie es falsch machen. Ich finde in der Regel muss ich verdauen, wenn ich den Zustand des Bereichs als Folge eines DOM-Ereignisses außerhalb der Reichweite von Angular zu aktualisieren müssen feuern. Zum Beispiel, wenn ein Twitter Bootstrap Modal ausgeblendet wird. Manchmal wird das DOM-Ereignis ausgelöst, wenn ein $digest ist im Gange, manchmal nicht. Deshalb verwende ich diese Prüfung.

Ich würde gerne einen besseren Weg kennen, wenn jemand einen kennt.


Aus den Kommentaren: by @anddoutoi

angular.js Anti-Muster

  1. Tun Sie es nicht if (!$scope.$$phase) $scope.$apply() bedeutet dies, dass Ihr $scope.$apply() nicht weit genug oben im Aufrufstapel liegt.

333voto

frosty Punkte 19476

Der Digest-Zyklus ist ein synchroner Aufruf. Er gibt die Kontrolle erst dann an die Ereignisschleife des Browsers ab, wenn er beendet ist. Es gibt mehrere Möglichkeiten, damit umzugehen. Der einfachste Weg, damit umzugehen, ist die Verwendung des eingebauten $timeout, und ein zweiter Weg ist, wenn Sie underscore oder lodash verwenden (und das sollten Sie), rufen Sie das Folgende auf:

$timeout(function(){
    //any code in here will automatically have an apply run afterwards
});

oder wenn Sie Lodash haben:

_.defer(function(){$scope.$apply();});

Wir haben mehrere Workarounds ausprobiert, und wir hassten es, $rootScope in alle unsere Controller, Direktiven und sogar einige Factories zu injizieren. Daher waren $timeout und _.defer bisher unsere Favoriten. Diese Methoden sagen Angular erfolgreich, dass es bis zur nächsten Animationsschleife warten soll, was garantiert, dass der aktuelle scope.$apply vorbei ist.

273voto

floribon Punkte 18987

Viele der Antworten hier enthalten gute Ratschläge, können aber auch zu Verwirrung führen. Einfach verwenden $timeout est pas weder die beste noch die richtige Lösung. Lesen Sie das auch, wenn Sie sich Sorgen um die Leistung oder Skalierbarkeit machen.

Was Sie wissen sollten

  • $$phase ist dem Rahmen vorbehalten, und dafür gibt es gute Gründe.

  • $timeout(callback) wartet, bis der aktuelle Digest-Zyklus (falls vorhanden) abgeschlossen ist, führt dann den Callback aus und führt am Ende einen vollständigen $apply .

  • $timeout(callback, delay, false) tut dasselbe (mit einer optionalen Verzögerung vor der Ausführung des Rückrufs), löst aber keine $apply (drittes Argument), das die Leistung speichert, wenn Sie Ihr Angular-Modell ($scope) nicht geändert haben.

  • $scope.$apply(callback) ruft u.a. auf, $rootScope.$digest Das bedeutet, dass der Wurzelbereich der Anwendung und alle untergeordneten Bereiche neu sortiert werden, auch wenn Sie sich in einem isolierten Bereich befinden.

  • $scope.$digest() wird einfach sein Modell mit dem View synchronisieren, aber nicht seinen Elternbereich verdauen, was eine Menge Leistung sparen kann, wenn man an einem isolierten Teil des HTML mit einem isolierten Bereich arbeitet (meistens von einer Direktive). $digest nimmt keinen Callback: Sie führen den Code aus und verdauen ihn dann.

  • $scope.$evalAsync(callback) wurde mit angularjs 1.2 eingeführt und wird wahrscheinlich die meisten Ihrer Probleme lösen. Bitte lesen Sie den letzten Abschnitt, um mehr darüber zu erfahren.

  • wenn Sie die $digest already in progress error dann ist Ihre Architektur falsch: entweder brauchen Sie Ihren Geltungsbereich nicht neu zu ordnen, oder Sie sollten dafür nicht zuständig sein (siehe unten).

Wie Sie Ihren Code strukturieren

Wenn Sie diesen Fehler erhalten, versuchen Sie, Ihren Bereich zu verdauen, während er bereits in Bearbeitung ist: Da Sie den Zustand Ihres Bereichs zu diesem Zeitpunkt nicht kennen, sind Sie nicht für seine Verdauung zuständig.

function editModel() {
  $scope.someVar = someVal;
  /* Do not apply your scope here since we don't know if that
     function is called synchronously from Angular or from an
     asynchronous code */
}

// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
  // No need to digest
  editModel();
}

// Any kind of asynchronous code, for instance a server request
callServer(function() {
  /* That code is not watched nor digested by Angular, thus we
     can safely $apply it */
  $scope.$apply(editModel);
});

Und wenn Sie wissen, was Sie tun und an einer isolierten kleinen Direktive als Teil einer großen Angular-Anwendung arbeiten, können Sie $digest gegenüber $apply bevorzugen, um Leistung zu sparen.

Aktualisierung seit Angularjs 1.2

Eine neue, leistungsfähige Methode wurde zu jedem $scope hinzugefügt: $evalAsync . Grundsätzlich führt er seinen Callback innerhalb des aktuellen Digest-Zyklus aus, sofern ein solcher stattfindet, andernfalls beginnt ein neuer Digest-Zyklus mit der Ausführung des Callbacks.

Das ist immer noch nicht so gut wie ein $scope.$digest wenn Sie wirklich wissen, dass Sie nur einen isolierten Teil Ihrer HTML-Datei synchronisieren müssen (da eine neue $apply ausgelöst wird, wenn gerade keine Funktion ausgeführt wird), aber dies ist die beste Lösung, wenn Sie eine Funktion ausführen, die Sie können nicht wissen, ob es synchron ausgeführt wird oder nicht z. B. nach dem Abrufen einer Ressource, die möglicherweise im Cache gespeichert ist: Manchmal erfordert dies einen asynchronen Aufruf an einen Server, andernfalls wird die Ressource lokal synchron abgerufen.

In diesen und allen anderen Fällen, in denen Sie eine !$scope.$$phase verwenden, müssen Sie $scope.$evalAsync( callback )

89voto

lambinator Punkte 10045

Eine praktische kleine Hilfsmethode, um diesen Prozess TROCKEN zu halten:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}

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