AngularJS: Verhindern Sie, dass beim Aufrufen von $ scope bereits ein Fehler $ digest ausgeführt wird. $ Apply ()

838

Ich stelle fest, dass ich meine Seite mehr und mehr manuell auf meinen Bereich aktualisieren muss, seit ich eine Anwendung in Winkel erstellt habe.

Die einzige Möglichkeit, dies zu tun, besteht darin, $apply()aus dem Bereich meiner Controller und Anweisungen heraus aufzurufen . Das Problem dabei ist, dass immer wieder ein Fehler an die Konsole ausgegeben wird, der lautet:

Fehler: $ Digest wird bereits ausgeführt

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

Glühbirne1
quelle
34
Es ist wirklich frustrierend, dass wir $ apply immer mehr verwenden müssen.
OZ_
Ich erhalte auch diesen Fehler, obwohl ich in einem Rückruf $ apply aufrufe. Ich verwende eine Bibliothek eines Drittanbieters, um auf Daten auf ihren Servern zuzugreifen. Daher kann ich $ http nicht nutzen und möchte dies auch nicht, da ich ihre Bibliothek neu schreiben müsste, um $ http verwenden zu können.
Trevor
45
Verwenden Sie$timeout()
Onur Yıldırım
6
Verwenden Sie $ timeout (fn) + 1. Es kann das Problem beheben! $ scope. $$ phase ist nicht die beste Lösung.
Huei Tan
1
Nur Wrap-Code / Aufrufbereich. $ Werden innerhalb von Zeitüberschreitungen (nicht $ Zeitüberschreitung) AJAX-Funktionen (nicht $ http) und Ereignissen (nicht ng-*) angewendet . Stellen Sie sicher, dass es nicht auch beim Laden ausgeführt wird , wenn Sie es innerhalb einer Funktion aufrufen (die über timeout / ajax / events aufgerufen wird) .
Patrick

Antworten:

660

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

Sie können überprüfen, ob a $digestbereits ausgeführt wird, indem Sie überprüfen $scope.$$phase.

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

$scope.$$phasewird zurückkehren "$digest"oder "$apply"wenn ein $digestoder $applyin Bearbeitung ist. Ich glaube, der Unterschied zwischen diesen Zuständen besteht darin, dass $digestdie Uhren des aktuellen Bereichs und seiner Kinder verarbeitet werden und $applydie Beobachter aller Bereiche verarbeitet werden.

Bis zu @ dnc253 können Sie es falsch machen , wenn Sie anrufen $digestoder $applyhäufig sind. Im Allgemeinen muss ich verdauen, wenn ich den Status des Bereichs aufgrund eines DOM-Ereignisses aktualisieren muss, das außerhalb der Reichweite von Angular ausgelöst wird. Zum Beispiel, wenn ein Twitter-Bootstrap-Modal ausgeblendet wird. Manchmal wird das DOM-Ereignis ausgelöst, wenn a ausgeführt $digestwird, manchmal nicht. Deshalb benutze ich diesen Check.

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


Aus Kommentaren: von @anddoutoi

angle.js Anti Patterns

  1. Tun Sie das nicht if (!$scope.$$phase) $scope.$apply(), es bedeutet, dass Sie $scope.$apply()im Aufrufstapel nicht hoch genug sind.
Lee
quelle
230
Mir scheint, $ Digest / $ Apply sollte dies standardmäßig tun
Roy Truelove
21
Beachten Sie, dass ich in einigen Fällen nur den aktuellen Bereich UND den Stammbereich überprüfen muss. Ich habe einen Wert für die $$ -Phase im Stammverzeichnis erhalten, aber nicht in meinem Bereich. Denke, es hat etwas mit dem isolierten Geltungsbereich einer Richtlinie zu tun, aber ...
Roy Truelove
106
"Hör auf zu tun if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi
34
@anddoutoi: Einverstanden; Ihr Link macht ziemlich deutlich, dass dies nicht die Lösung ist. Ich bin mir jedoch nicht sicher, was unter "Sie sind im Aufrufstapel nicht hoch genug" zu verstehen ist. Weißt du was das heißt?
Trevor
13
@threed: siehe die Antwort von Aaronfrost. Der richtige Weg ist, Defer zu verwenden, um den Digest im nächsten Zyklus auszulösen. Andernfalls geht das Ereignis verloren und aktualisiert den Bereich überhaupt nicht.
Marek
663

Aus einer kürzlichen Diskussion mit den Angular-Leuten zu diesem Thema: Aus zukunftssicheren Gründen sollten Sie nicht verwenden$$phase

Wenn Sie auf die "richtige" Vorgehensweise klicken, lautet die Antwort derzeit

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

Ich bin kürzlich darauf gestoßen, als ich eckige Dienste schrieb, um die Facebook-, Google- und Twitter-APIs zu verpacken, bei denen in unterschiedlichem Maße Rückrufe eingereicht wurden.

Hier ist ein Beispiel aus einem Dienst. (Der Kürze halber wurde der Rest des Dienstes - der Variablen einrichtete, $ timeout usw. injizierte - 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 die Verzögerung Argument für $ timeout ist optional und wird auf 0 standardmäßig , wenn nicht konfiguriert ( $ timeout ruft $ browser.defer , die standardmäßig auf 0 , wenn Verzögerung nicht gesetzt ist )

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

Betaorbust
quelle
5
Ich bin in meinen Anweisungen oft darauf gestoßen. Schrieb einen für den Redakteur und dies stellte sich als perfekt heraus. Ich war bei einem Treffen mit Brad Green und er sagte, dass Angular 2.0 ohne Digest-Zyklus riesig sein wird, wenn man JS 'native Beobachtungsfähigkeit verwendet und eine Polyfüllung für Browser verwendet, denen dies fehlt. Ab diesem Zeitpunkt müssen wir dies nicht mehr tun. :)
Michael J. Calkins
Gestern habe ich ein Problem gesehen, bei dem das Aufrufen von selectize.refreshItems () innerhalb von $ timeout den gefürchteten rekursiven Digest-Fehler verursacht hat. Irgendwelche Ideen, wie das sein könnte?
iwein
3
Wenn Sie anstelle von $timeoutnative verwenden setTimeout, warum verwenden Sie nicht $windowanstelle von native window?
LeeGee
2
@LeeGee: In $timeoutdiesem Fall wird $timeoutsichergestellt, dass der Winkelbereich ordnungsgemäß aktualisiert wird. Wenn kein $ Digest ausgeführt wird, wird ein neuer $ Digest ausgeführt.
Ehrfurcht
2
@webicy Das ist keine Sache. Wenn der an $ timeout übergebene Hauptteil der Funktion ausgeführt wird, ist das Versprechen bereits gelöst! Es gibt absolut keinen Grund canceldafür. Aus den Dokumenten : "Infolgedessen wird das Versprechen mit einer Ablehnung gelöst." Sie können ein gelöstes Versprechen nicht lösen. Ihre Stornierung wird keine Fehler verursachen, aber auch nichts Positives bewirken.
Daemonexmachina
324

Der Digest-Zyklus ist ein synchroner Aufruf. Die Ereignisschleife des Browsers wird erst dann gesteuert, wenn sie abgeschlossen ist. Es gibt einige Möglichkeiten, damit umzugehen. Der einfachste Weg, um damit umzugehen, ist die Verwendung des integrierten $ timeout. Ein zweiter Weg ist, wenn Sie Unterstrich oder lodash verwenden (und Sie sollten es sein), rufen Sie Folgendes an:

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

oder wenn Sie lodash haben:

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

Wir haben verschiedene Problemumgehungen ausprobiert und es gehasst, $ rootScope in alle unsere Controller, Direktiven und sogar einige Fabriken zu injizieren. Das $ timeout und _.defer waren bisher unser Favorit. Diese Methoden weisen Angular erfolgreich an, bis zur nächsten Animationsschleife zu warten, wodurch sichergestellt wird, dass der aktuelle Gültigkeitsbereich. $ Apply abgelaufen ist.

eisig
quelle
2
Ist dies vergleichbar mit der Verwendung von $ timeout (...)? Ich habe in mehreren Fällen $ timeout verwendet, um auf den nächsten Ereigniszyklus zu verschieben, und es scheint gut zu funktionieren - weiß jemand, ob es einen Grund gibt, $ timeout nicht zu verwenden?
Trevor
9
Dies sollte wirklich nur verwendet werden, wenn Sie bereits verwenden underscore.js. Diese Lösung ist es nicht wert, die gesamte Unterstrichbibliothek zu importieren, nur um ihre deferFunktion zu nutzen . Ich bevorzuge die $timeoutLösung sehr, da jeder bereits über Angular Zugriff hat $timeout, ohne von anderen Bibliotheken abhängig zu sein.
Tennisgent
10
Stimmt ... aber wenn Sie keinen Unterstrich oder Lodash verwenden ... müssen Sie neu bewerten, was Sie tun. Diese beiden Bibliotheken haben das Aussehen des Codes verändert.
Frosty
2
Wir haben lodash als Abhängigkeit für Restangular (wir werden Restangular bald zugunsten der ng-Route eliminieren). Ich denke, es ist eine gute Antwort, aber es ist nicht großartig anzunehmen, dass die Leute Unterstrich / lodash verwenden wollen. Mit allen Mitteln diese Libs sind in Ordnung ... wenn man ihnen genug nutzen ... in diesen Tagen benutze ich ES5 Methoden , die 98% des Grund auszulöschen ich verwendet , um Strich zu enthalten.
BradGreens
2
Sie haben Recht @SgtPooki. Ich habe die Antwort geändert, um die Option einzuschließen, auch $ timeout zu verwenden. $ timeout und _.defer warten beide bis zur nächsten Animationsschleife, wodurch sichergestellt wird, dass der aktuelle Bereich. $ apply beendet wurde. Vielen Dank, dass Sie mich ehrlich halten und mich dazu bringen, die Antwort hier zu aktualisieren.
Frosty
267

Viele der Antworten hier enthalten gute Ratschläge, können aber auch zu Verwirrung führen. Einfach zu verwenden $timeoutist weder die beste noch die richtige Lösung. Lesen Sie dies auch, wenn Sie sich Gedanken über Leistung oder Skalierbarkeit machen.

Dinge, die Sie wissen sollten

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

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

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

  • $scope.$apply(callback)Ruft unter anderem auf, $rootScope.$digestwas bedeutet, dass der Stammbereich der Anwendung und alle untergeordneten Elemente neu definiert werden, selbst wenn Sie sich in einem isolierten Bereich befinden.

  • $scope.$digest()synchronisiert einfach das Modell mit der Ansicht, verdaut jedoch nicht den übergeordneten Bereich, wodurch eine Menge Leistung eingespart werden kann, wenn an einem isolierten Teil Ihres HTML-Codes mit einem isolierten Bereich gearbeitet wird (meistens aus einer Direktive). $ Digest nimmt keinen Rückruf entgegen: Sie führen den Code aus und dann Digest.

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

  • Wenn Sie das erhalten $digest already in progress error, ist Ihre Architektur falsch: Entweder müssen Sie Ihren Bereich nicht neu definieren, oder Sie sollten nicht dafür verantwortlich sein (siehe unten).

So strukturieren Sie Ihren Code

Wenn Sie diesen Fehler erhalten, versuchen Sie, Ihren Bereich zu verdauen, während er bereits ausgeführt wird: Da Sie den Status Ihres Bereichs zu diesem Zeitpunkt nicht kennen, sind Sie nicht für die Verarbeitung verantwortlich.

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 $ Digest anstelle von $ Apply vorziehen, um Leistungen zu sparen.

Update seit Angularjs 1.2

Jedem $ scope wurde eine neue, leistungsstarke Methode hinzugefügt: $evalAsync . Grundsätzlich führt es seinen Rückruf innerhalb des aktuellen Digest-Zyklus aus, wenn einer auftritt, andernfalls beginnt ein neuer Digest-Zyklus mit der Ausführung des Rückrufs.

Das ist immer noch nicht so gut wie $scope.$digestwenn Sie wirklich wissen, dass Sie nur einen isolierten Teil Ihres HTML-Codes synchronisieren müssen (da ein neuer $applyTeil ausgelöst wird, wenn keiner ausgeführt wird), aber dies ist die beste Lösung, wenn Sie eine Funktion ausführen die Sie können es nicht wissen , ob wird synchron oder nicht ausgeführt werden , möglicherweise im Cache gespeichert , nachdem das Abrufen eine Ressource, zum Beispiel: manchmal wird dies einen asynchronen Aufruf an einen Server benötigen, da sonst die Ressource lokal synchron abgerufen werden.

!$scope.$$phaseVerwenden Sie in diesen und allen anderen Fällen, in denen Sie eine hatten , diese unbedingt$scope.$evalAsync( callback )

Floribon
quelle
4
$timeoutwird im Vorbeigehen kritisiert. Können Sie weitere Gründe angeben, die Sie vermeiden sollten $timeout?
mlhDev
88

Praktische kleine Hilfsmethode, um diesen Prozess trocken zu halten:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
Lambinator
quelle
6
Ihre safeApply hat mir mehr als alles andere geholfen zu verstehen, was los war. Danke, dass du das gepostet hast.
Jason More
4
Ich wollte das Gleiche tun, aber heißt das nicht, dass die Änderungen, die wir in fn () vornehmen, möglicherweise nicht von $ Digest gesehen werden? Wäre es nicht besser, die Funktion zu verzögern, wenn man den Umfang annimmt. $$ phase === '$ Digest'?
Spencer Alger
Ich stimme zu, manchmal wird $ apply () verwendet, um den Digest auszulösen, indem nur das fn von selbst aufgerufen wird ... führt das nicht zu einem Problem?
CMCDragonkai
1
Ich denke, scope.$apply(fn);sollte sein, scope.$apply(fn());weil fn () die Funktion ausführen wird und nicht fn. Bitte helfen Sie mir, wo ich falsch
liege
1
@ZenOut Der Aufruf von $ apply unterstützt viele verschiedene Arten von Argumenten, einschließlich Funktionen. Wenn eine Funktion übergeben wird, wird die Funktion ausgewertet.
Boxmein
33

Ich hatte das gleiche Problem mit Skripten von Drittanbietern wie CodeMirror und Krpano, und selbst die Verwendung der hier genannten safeApply-Methoden hat den Fehler für mich nicht behoben.

Aber was es gelöst hat, ist die Verwendung des $ timeout-Dienstes (vergessen Sie nicht, ihn zuerst zu injizieren).

So etwas wie:

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

und wenn in Ihrem Code, den Sie verwenden

diese

Vielleicht, weil es sich im Controller einer Factory-Direktive befindet oder nur eine Art Bindung benötigt, 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();

  }]
)
Ciul
quelle
32

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

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

Dies kann beispielsweise passieren, wenn Sie über eine Bibliothek verfügen, 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 Element jedoch bereits im Cache, sodass es synchron abgerufen werden kann.

Um diesen Fehler zu vermeiden, müssen Sie sicherstellen, dass der aufgerufene Code $applyasynchron ausgeführt wird. Dies kann erreicht werden, indem Sie Ihren Code in einem Aufruf von $timeoutmit der Verzögerung 0(die Standardeinstellung) ausführen . Durch das Aufrufen Ihres Codes im Inneren $timeoutentfällt jedoch die Notwendigkeit eines Aufrufs $apply, da $ timeout selbst einen weiteren $digestZyklus auslöst , der wiederum alle erforderlichen 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...

Rufen $applySie nur auf, wenn Sie wissen, dass der Code, der ausgeführt wird, immer außerhalb des Angular-Codes ausgeführt wird (z. B. erfolgt Ihr Aufruf von $ apply innerhalb eines Rückrufs, der von Code außerhalb Ihres Angular-Codes aufgerufen wird).

Ich verstehe nicht, warum Sie nicht immer (ohne Verzögerung) verwenden können , es sei denn, jemand ist sich eines schwerwiegenden Nachteils bei der Verwendung von $timeoutover bewusst , da dies ungefähr das Gleiche bewirkt.$apply$timeout$apply

Trevor
quelle
Danke, das hat in meinem Fall funktioniert, in dem ich mich nicht $applyselbst anrufe, aber trotzdem den Fehler erhalte.
Aristcris
5
Der Hauptunterschied besteht darin, dass er $applysynchron ist (sein Rückruf wird ausgeführt, dann der Code nach $ apply), während dies $timeoutnicht der Fall ist: Wenn der aktuelle Code nach dem Timeout ausgeführt wird, beginnt ein neuer Stapel mit seinem Rückruf, als ob Sie ihn verwenden würden setTimeout. Dies kann zu Grafikfehlern führen, wenn Sie zweimal dasselbe Modell aktualisieren: $timeoutWarten Sie, bis die Ansicht aktualisiert wurde, bevor Sie sie erneut aktualisieren.
Floribon
Vielen Dank, drei. Ich hatte eine Methode, die als Ergebnis einer $ watch-Aktivität aufgerufen wurde, und versuchte, die Benutzeroberfläche zu aktualisieren, bevor die Ausführung meines externen Filters abgeschlossen war. Das in eine $ timeout-Funktion zu integrieren, hat bei mir funktioniert.
Djmarquette
28

Wenn Sie diesen Fehler erhalten, bedeutet dies im Grunde, dass Ihre Ansicht bereits aktualisiert wird. Sie sollten wirklich nicht $apply()in Ihrem Controller anrufen müssen . Wenn Ihre Ansicht nicht wie erwartet aktualisiert wird und Sie diesen Fehler nach dem Aufruf erhalten $apply(), bedeutet dies höchstwahrscheinlich, dass Sie das Modell nicht korrekt aktualisieren. Wenn Sie einige Details veröffentlichen, können wir das Kernproblem herausfinden.

dnc253
quelle
heh, ich habe den ganzen Tag damit verbracht herauszufinden, dass AngularJS Bindungen einfach nicht "magisch" sehen kann und ich sollte ihn manchmal mit $ apply () pushen.
OZ_
was bei allen Mitteln you're not updating the the model correctly? $scope.err_message = 'err message';ist nicht korrektes Update?
OZ_
2
Sie müssen nur dann aufrufen, $apply()wenn Sie das Modell "außerhalb" von Angular aktualisieren (z. B. über ein jQuery-Plugin). Es ist leicht, in die Falle der Ansicht zu geraten, die nicht richtig aussieht, und so werfen Sie $apply()überall ein paar s, was dann zu dem Fehler führt, der im OP angezeigt wird. Wenn ich sage, you're not updating the the model correctlymeine ich nur, dass die gesamte Geschäftslogik alles, was in den Geltungsbereich fällt, nicht korrekt ausfüllt, was dazu führt, dass die Ansicht nicht wie erwartet aussieht.
dnc253
@ dnc253 Ich stimme zu und habe die Antwort geschrieben. Wenn ich weiß, was ich jetzt weiß, würde ich $ timeout (function () {...}) verwenden. Es macht dasselbe wie _.defer. Beide verschieben sich zur nächsten Animationsschleife.
frostig
14

Die kürzeste Form des Safes $applyist:

$timeout(angular.noop)
Hexenmeister
quelle
11

Sie können auch evalAsync verwenden. Es wird irgendwann ausgeführt, nachdem die Verdauung beendet ist!

scope.evalAsync(function(scope){
    //use the scope...
});
CMCDragonkai
quelle
10

Beheben Sie es zunächst nicht auf diese Weise

if ( ! $scope.$$phase) { 
  $scope.$apply(); 
}

Dies ist nicht sinnvoll, da $ phase nur ein boolesches Flag für den $ digest-Zyklus ist, sodass Ihr $ apply () manchmal nicht ausgeführt wird. Und denken Sie daran, es ist eine schlechte Praxis.

Verwenden Sie stattdessen $timeout

    $timeout(function(){ 
  // Any code in here will automatically have an $scope.apply() run afterwards 
$scope.myvar = newValue; 
  // And it just works! 
});

Wenn Sie Unterstrich oder lodash verwenden, können Sie defer () verwenden:

_.defer(function(){ 
  $scope.$apply(); 
});
Sagar M.
quelle
9

Manchmal werden immer noch Fehler angezeigt, wenn Sie diese Methode verwenden ( https://stackoverflow.com/a/12859093/801426) ).

Versuche dies:

if(! $rootScope.$root.$$phase) {
...
Bullgare
quelle
5
Die Verwendung von! $ scope. $$ phase und! $ scope. $ root. $$ phase (nicht! $ rootScope. $ root. $$ phase) funktioniert bei mir. +1
asprotte
2
$rootScopeund anyScope.$rootsind der gleiche Typ. $rootScope.$rootist redundant.
Floribon
5

versuchen Sie es mit

$scope.applyAsync(function() {
    // your code
});

anstatt

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

$ applyAsync Planen Sie den Aufruf von $ apply zu einem späteren Zeitpunkt. Dies kann verwendet werden, um mehrere Ausdrücke in die Warteschlange zu stellen, die im selben Digest ausgewertet werden müssen.

HINWEIS: Innerhalb des $ Digest wird $ applyAsync () nur geleert, wenn der aktuelle Bereich $ rootScope ist. Dies bedeutet, dass beim Aufrufen von $ Digest in einem untergeordneten Bereich die $ applyAsync () - Warteschlange nicht implizit geleert wird.

Beispiel:

  $scope.$applyAsync(function () {
                if (!authService.authenticated) {
                    return;
                }

                if (vm.file !== null) {
                    loadService.setState(SignWizardStates.SIGN);
                } else {
                    loadService.setState(SignWizardStates.UPLOAD_FILE);
                }
            });

Verweise:

1. Scope. $ ApplyAsync () vs. Scope. $ EvalAsync () in AngularJS 1.3

  1. AngularJs Docs
Eduardo Eljaiek
quelle
4

Ich würde Ihnen raten, ein benutzerdefiniertes Ereignis zu verwenden, anstatt einen Digest-Zyklus auszulösen.

Ich habe festgestellt, dass das Senden von benutzerdefinierten Ereignissen und das Registrieren von Listenern für diese Ereignisse eine gute Lösung ist, um eine Aktion auszulösen, die ausgeführt werden soll, unabhängig davon, ob Sie sich in einem Digest-Zyklus befinden oder nicht.

Durch das Erstellen eines benutzerdefinierten Ereignisses werden Sie auch effizienter mit Ihrem Code, da Sie nur Listener auslösen, die dieses Ereignis abonniert haben, und NICHT alle an den Bereich gebundenen Uhren auslösen, wie Sie es tun würden, wenn Sie den Bereich aufrufen. $ Apply.

$scope.$on('customEventName', function (optionalCustomEventArguments) {
   //TODO: Respond to event
});


$scope.$broadcast('customEventName', optionalCustomEventArguments);
nelsonomuto
quelle
3

yearofmoo hat großartige Arbeit geleistet, um eine wiederverwendbare $ safeApply-Funktion für uns zu erstellen:

https://github.com/yearofmoo/AngularJS-Scope.SafeApply

Verwendungszweck :

//use by itself
$scope.$safeApply();

//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);

//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {

});

//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {

});

//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);
RNobel
quelle
2

Ich konnte dieses Problem lösen, indem ich $evalstatt $applyan Orten anrief, an denen ich weiß, dass die $digestFunktion ausgeführt wird.

Nach Ansicht der docs , $applytut im Grunde diese:

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

In meinem Fall ng-clickändert a eine Variable innerhalb eines Bereichs, und ein $ watch für diese Variable ändert andere Variablen, die sein müssen $applied. Dieser letzte Schritt verursacht den Fehler "Digest bereits in Bearbeitung".

Durch Ersetzen $applydurch $evalinnerhalb des Überwachungsausdrucks werden die Bereichsvariablen wie erwartet aktualisiert.

Daher scheint es so , als ob $evalSie nur etwas tun müssen , wenn der Digest aufgrund einer anderen Änderung in Angular trotzdem ausgeführt wird.

Teleclimber
quelle
2

Verwenden Sie $scope.$$phase || $scope.$apply();stattdessen

Visakh B Sujathan
quelle
1

Als ich verstand, dass die Angular-Dokumente das Überprüfen $$phaseeines Anti-Musters aufrufen , versuchte ich zu bekommen $timeoutund _.deferzu arbeiten.

Das Timeout und die verzögerten Methoden erzeugen {{myVar}}wie ein FOUT einen Blitz nicht analysierten Inhalts im Dom . Für mich war das nicht akzeptabel. Es lässt mich nicht viel dogmatisch sagen, dass etwas ein Hack ist und keine geeignete Alternative hat.

Das einzige, was jedes Mal funktioniert, ist:

if(scope.$$phase !== '$digest'){ scope.$digest() }.

Ich verstehe die Gefahr dieser Methode nicht oder warum sie von den Leuten in den Kommentaren und dem eckigen Team als Hack beschrieben wird. Der Befehl scheint präzise und leicht zu lesen:

"Mach den Digest, es sei denn, einer passiert bereits"

In CoffeeScript ist es noch schöner:

scope.$digest() unless scope.$$phase is '$digest'

Was ist das Problem damit? Gibt es eine Alternative, die kein FOUT erzeugt? $ safeApply sieht gut aus, verwendet aber auch die $$phaseInspektionsmethode.

Einfach
quelle
1
Ich würde gerne eine informierte Antwort auf diese Frage sehen!
Ben Wheeler
Es ist ein Hack, weil es bedeutet, dass Sie den Kontext verpassen oder den Code an diesem Punkt nicht verstehen: Entweder befinden Sie sich innerhalb eines Winkel-Digest-Zyklus und Sie brauchen das nicht, oder Sie befinden sich asynchron außerhalb davon und dann brauchen Sie ihn. Wenn Sie das an diesem Punkt des Codes nicht wissen können, dann sind Sie nicht dafür verantwortlich, es zu verdauen
floribon
1

Dies ist mein Dienstprogramm:

angular.module('myApp', []).service('Utils', function Utils($timeout) {
    var Super = this;

    this.doWhenReady = function(scope, callback, args) {
        if(!scope.$$phase) {
            if (args instanceof Array)
                callback.apply(scope, Array.prototype.slice.call(args))
            else
                callback();
        }
        else {
            $timeout(function() {
                Super.doWhenReady(scope, callback, args);
            }, 250);
        }
    };
});

und dies ist ein Beispiel für seine Verwendung:

angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
    $scope.foo = function() {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.foo);

    $scope.fooWithParams = function(p1, p2) {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};
Ranbuch
quelle
1

Ich habe diese Methode angewendet und sie scheint einwandfrei zu funktionieren. Dies wartet nur auf die Zeit, die der Zyklus beendet ist, und wird dann ausgelöst apply(). Rufen Sie die Funktion einfach apply(<your scope>)von einem beliebigen Ort aus auf.

function apply(scope) {
  if (!scope.$$phase && !scope.$root.$$phase) {
    scope.$apply();
    console.log("Scope Apply Done !!");
  } 
  else {
    console.log("Scheduling Apply after 200ms digest cycle already in progress");
    setTimeout(function() {
        apply(scope)
    }, 200);
  }
}
Ashu
quelle
1

Wenn ich den Debugger deaktiviert habe, tritt der Fehler nicht mehr auf. In meinem Fall lag es daran, dass der Debugger die Codeausführung stoppte.

jmojico
quelle
0

Ähnlich wie bei den obigen Antworten, aber das hat bei mir treu funktioniert ... in einem Service hinzufügen:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };
Shawn Dotey
quelle
0

Sie können verwenden

$timeout

um den Fehler zu verhindern.

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);
Satish Singh
quelle
Was ist, wenn ich $
timeout
0

Das Problem tritt im Grunde genommen auf, wenn wir um Winkel bitten, um den Digest-Zyklus auszuführen, obwohl er gerade ausgeführt wird, was zu Problemen mit dem Winkel zum Verständnis führt. Konsequenz Ausnahme in der Konsole.
1. Es hat keinen Sinn, scope. $ Apply () innerhalb der $ timeout-Funktion aufzurufen, da es intern dasselbe tut.
2. Der Code wird mit der Vanilla-JavaScript-Funktion verwendet, da der native, nicht winkelige Winkel definiert ist, dh setTimeout.
3. Dazu können Sie

if (! Scope. $$ phase) {
scope. $ EvalAsync (function () {

}) verwenden. }}

Sachin Mishra
quelle
0
        let $timeoutPromise = null;
        $timeout.cancel($timeoutPromise);
        $timeoutPromise = $timeout(() => {
            $scope.$digest();
        }, 0, false);

Hier ist eine gute Lösung, um diesen Fehler zu vermeiden und $ apply zu vermeiden

Sie können dies mit Debounce (0) kombinieren, wenn Sie aufgrund eines externen Ereignisses aufrufen. Oben ist das von uns verwendete "Entprellen" und ein vollständiges Beispiel für Code

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

        return function (func, wait, apply) {
            // apply default is true for $timeout
            if (apply !== false) {
                apply = true;
            }

            var promise;
            return function () {
                var cntx = this,
                    args = arguments;
                $timeout.cancel(promise);
                promise = $timeout(function () {
                    return func.apply(cntx, args);
                }, wait, apply);
                return promise;
            };
        };
    }
])

und den Code selbst, um ein Ereignis abzuhören und $ digest nur für den von Ihnen benötigten $ scope aufzurufen

        let $timeoutPromise = null;
        let $update = debounce(function () {
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
        }, 0, false);

        let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
            $update();
        });


        $scope.$on('$destroy', () => {
            $timeout.cancel($update);
            $timeout.cancel($timeoutPromise);
            $unwatchModelChanges();
        });
Sergey Sahakyan
quelle
-3

Gefunden: https://coderwall.com/p/ngisma wo Nathan Walker (am Ende der Seite) einen Dekorateur in $ rootScope vorschlägt, um func 'safeApply' zu erstellen, Code:

yourAwesomeModule.config([
  '$provide', function($provide) {
    return $provide.decorator('$rootScope', [
      '$delegate', function($delegate) {
        $delegate.safeApply = function(fn) {
          var phase = $delegate.$$phase;
          if (phase === "$apply" || phase === "$digest") {
            if (fn && typeof fn === 'function') {
              fn();
            }
          } else {
            $delegate.$apply(fn);
          }
        };
        return $delegate;
      }
    ]);
  }
]);
Warren Davis
quelle
-7

Dies wird Ihr Problem lösen:

if(!$scope.$$phase) {
  //TODO
}
eebbesen
quelle
Tun Sie dies nicht, wenn (! $ Scope. $$ Phase) $ scope. $ Apply () bedeutet, dass Ihr $ scope. $ Apply () im Aufrufstapel nicht hoch genug ist.
MGot90