Frage AngularJS: Verhindert den Fehler $ digest, der bereits beim Aufruf von $ scope ausgeführt wird. $ Apply ()


Ich finde, dass ich meine Seite manuell immer mehr auf meinen Bereich aktualisieren muss, seit eine Anwendung in eckig erstellt wurde.

Der einzige Weg, den ich kenne, ist, anzurufen $apply() aus dem Umfang meiner Controller und Direktiven. Das Problem dabei ist, dass es immer wieder einen Fehler auf die Konsole wirft, der lautet:

Fehler: $ Digest ist bereits in Bearbeitung

Weiß jemand, wie man diesen Fehler vermeidet oder dasselbe erreicht, aber auf andere Weise?


784
2017-10-04 14:07


Ursprung


Antworten:


Benutze dieses Muster nicht - Dies führt zu mehr Fehlern als es löst. Obwohl du denkst, dass es etwas repariert hat, tat es nicht.

Sie können überprüfen, ob a $digest ist bereits in Bearbeitung, indem es überprüft $scope.$$phase.

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

$scope.$$phase wird zurückkehren "$digest" oder "$apply" wenn ein $digest oder $apply ist in Arbeit. Ich glaube, der Unterschied zwischen diesen Staaten ist der $digest verarbeitet die Uhren des aktuellen Bereichs und seiner Kinder und $apply verarbeitet die Beobachter aller Bereiche.

Zu @ dnc253's Punkt, wenn Sie sich anrufen $digest oder $apply oft tust du es falsch. Ich finde im Allgemeinen, dass ich verdauen muss, wenn ich den Status des Bereichs aktualisieren muss, weil ein DOM-Ereignis außerhalb der Reichweite von Angular ausgelöst wurde. Zum Beispiel, wenn ein Twitter-Bootstrap-Modal ausgeblendet wird. Manchmal wird das DOM-Ereignis ausgelöst, wenn a $digest ist in Arbeit, manchmal nicht. Deshalb benutze ich diesen Check.

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


Von Kommentaren: von @anddoutoi

angular.js Anti-Muster

  1. Tu es nicht if (!$scope.$$phase) $scope.$apply()Es bedeutet dein $scope.$apply() ist im Call-Stack nicht hoch genug.

635
2017-10-12 12:28



Aus einer kürzlichen Diskussion mit den Angulars zu diesem Thema: Aus Gründen der Zukunftssicherheit sollten Sie nicht verwenden $$phase

Wenn auf den "richtigen" Weg gedrängt wird, ist die Antwort momentan

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

Ich bin kürzlich auf dieses Thema gestoßen, als ich eckige Dienste geschrieben habe, um die Facebook-, Google- und Twitter-APIs zu verpacken, die in unterschiedlichem Maße Rückrufe haben.

Hier ist ein Beispiel aus einem Service. (Der Kürze halber wurde der Rest des Dienstes - der Variablen eingerichtet hat, injected $ timeout usw. - 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 0 ist, wenn es nicht gesetzt ist ($ Zeitüberschreitung Anrufe $ browser.defer welche Standardwert ist 0, wenn keine Verzögerung eingestellt ist)

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


631
2017-09-25 04:06



Der Digest-Zyklus ist ein synchroner Aufruf. Es wird keine Kontrolle über die Ereignisschleife des Browsers erhalten, bis es fertig ist. Es gibt ein paar Möglichkeiten, damit umzugehen. Der einfachste Weg, um damit umzugehen, ist die Verwendung des eingebauten $ timeout, und eine zweite Möglichkeit ist, wenn Sie Unterstreichung oder lodash verwenden (und Sie sollten sein), rufen Sie Folgendes auf:

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

oder wenn Sie unterstreichen:

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

Wir haben mehrere Umgehungslösungen ausprobiert, und wir hassten es, $ rootScope in alle unsere Controller, Direktiven und sogar einige Fabriken zu injizieren. Also, das $ timeout und _defer waren bisher unsere Favoriten. Diese Methoden teilen angular erfolgreich mit, bis zur nächsten Animationsschleife zu warten, was garantiert, dass der aktuelle Bereich. $ Apply vorbei ist.


312
2017-07-30 22:51



Viele der Antworten enthalten gute Ratschläge, können aber auch zu Verwirrung führen. Einfach verwenden $timeout ist nicht die beste noch die richtige Lösung. Lesen Sie auch, wenn Sie von Leistung oder Skalierbarkeit betroffen sind.

Dinge, die du wissen solltest

  • $$phase ist privat für den Rahmen und es gibt gute Gründe dafür.

  • $timeout(callback) Warten Sie, bis der aktuelle Digest-Zyklus (falls vorhanden) abgeschlossen ist, führen Sie dann den Rückruf aus, und führen Sie am Ende einen vollständigen Zyklus aus $apply.

  • $timeout(callback, delay, false) wird dasselbe tun (mit einer optionalen Verzögerung vor dem Ausführen des Rückrufs), aber nicht feuern $apply (drittes Argument), das Leistungen spart, wenn Sie Ihr Angular-Modell ($ scope) nicht geändert haben.

  • $scope.$apply(callback) beruft unter anderem $rootScope.$digestDies bedeutet, dass der Hauptbereich der Anwendung und alle untergeordneten Objekte neu erstellt werden, auch wenn Sie sich in einem isolierten Bereich befinden.

  • $scope.$digest() wird sein Modell einfach mit der Ansicht synchronisieren, aber nicht den Bereich der Eltern, was bei der Arbeit an einem isolierten Teil des HTML mit einem isolierten Bereich (hauptsächlich von einer Direktive) eine Menge Leistung sparen kann. $ digest nimmt keinen Rückruf: Sie führen den Code aus, dann verdauen Sie.

  • $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 du das bekommst $digest already in progress error, dann ist deine Architektur falsch: Entweder du musst deinen Umfang nicht neu erstellen, oder Sie sollten nicht dafür verantwortlich sein (siehe unten).

Wie strukturiere ich meinen Code?

Wenn Sie diesen Fehler erhalten, versuchen Sie, Ihren Scope zu verdauen, während er bereits aktiv ist. Da Sie den Status Ihres Scope zu diesem Zeitpunkt noch nicht kennen, sind Sie nicht für die Verarbeitung 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 arbeiten, während Sie Teil einer großen Angular-Anwendung sind, können Sie stattdessen $ digest anstelle von $ anwenden, um die Leistung zu sparen.

Update seit Angularjs 1.2

Eine neue, leistungsfähige Methode wurde jedem $ scope hinzugefügt: $evalAsync. Grundsätzlich wird der Callback innerhalb des aktuellen Digest-Zyklus ausgeführt, wenn einer auftritt, andernfalls startet ein neuer Digest-Zyklus die 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 Ihres HTML synchronisieren müssen (seit einem neuen $apply wird ausgelöst, wenn keiner läuft), aber das ist die beste Lösung, wenn Sie eine Funktion ausführen, die Sie können es nicht wissen, wenn es synchron ausgeführt wird oder nichtzum Beispiel nach dem Abrufen einer potenziell zwischengespeicherten Ressource: manchmal erfordert dies einen asynchronen Aufruf an einen Server, andernfalls wird die Ressource synchron synchron abgeholt.

In diesen Fällen und all den anderen, in denen Sie eine hatten !$scope.$$phase, sicher sein, zu verwenden $scope.$evalAsync( callback )


257
2018-04-16 06:59



Praktische kleine Hilfsmethode, um diesen Prozess aufrecht zu erhalten DRY:

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

86
2018-06-14 18:14



Sehen http://docs.angularjs.org/error/$rootScope:inprog

Das Problem tritt auf, wenn Sie einen Anruf haben $apply das wird manchmal asynchron außerhalb von Angular - Code ausgeführt (wenn $ apply verwendet werden sollte) und manchmal synchron innerhalb von Angular - Code (was den $digest already in progressError).

Dies kann beispielsweise der Fall sein, wenn Sie eine Bibliothek haben, die Elemente asynchron von einem Server abruft und zwischenspeichert. Wenn ein Element zum ersten Mal angefordert wird, wird es asynchron abgerufen, um die Codeausführung nicht zu blockieren. Beim zweiten Mal befindet sich das Objekt jedoch bereits im Cache, sodass es synchron abgerufen werden kann.

Der Weg, diesen Fehler zu vermeiden, besteht darin, sicherzustellen, dass der Code aufruft, der anruft $apply wird asynchron ausgeführt. Dies kann erreicht werden, indem Sie Ihren Code innerhalb eines Anrufs ausführen $timeout mit der Verzögerung auf 0 (Dies ist die Standardeinstellung). Rufen Sie jedoch Ihren Code auf $timeout Entfernt die Notwendigkeit, anzurufen $apply, weil $ timeout ein anderes auslöst $digest Zyklus für sich selbst, der wiederum alle notwendigen Aktualisierungen usw. durchführt.

Lösung

Kurz gesagt, anstatt dies zu tun:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

mach das:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

Nur anrufen $apply Wenn Sie wissen, dass der Code ausgeführt wird, wird er immer außerhalb von Angular-Code ausgeführt (z. B. wird Ihr Aufruf von $ apply in einem Callback ausgeführt, der von Code außerhalb Ihres Angular-Codes aufgerufen wird).

Es sei denn, jemand ist sich eines wirkungsvollen Nachteils bei der Verwendung bewusst $timeout Über $applyIch verstehe nicht, warum du es nicht immer benutzen kannst $timeout (mit Null Verzögerung) statt $apply, da es ungefähr das gleiche tut.


31
2018-01-21 21:33



Ich hatte das gleiche Problem mit Drittanbieter-Skripten wie CodeMirror zum Beispiel und Krpano, und selbst die Verwendung der hier erwähnten safeApply-Methoden hat den Fehler für mich nicht gelöst.

Aber was es gelöst hat, ist $ Timeout-Dienst (vergessen Sie nicht, es zuerst zu injizieren).

So etwas wie:

$timeout(function() {
  // run my code safely here
})

und wenn Sie in Ihrem Code sind, den Sie verwenden

Dies

vielleicht, weil es sich in einem Controller der Factory-Direktive befindet oder nur irgendeine Art von Bindung benötigt, dann würden Sie etwas tun wie:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)

31
2017-09-03 00:15



Wenn Sie diesen Fehler erhalten, heißt das im Grunde, dass Ihre Ansicht gerade aktualisiert wird. Du solltest wirklich nicht anrufen müssen $apply() in Ihrem Controller. Wenn Ihre Ansicht nicht wie erwartet aktualisiert wird und Sie diesen Fehler nach dem Aufruf erhalten $apply()Es bedeutet wahrscheinlich, dass Sie das Modell nicht korrekt aktualisieren. Wenn Sie einige Besonderheiten veröffentlichen, könnten wir das Kernproblem herausfinden.


28
2017-10-04 14:41