Angular $ scope. $ Apply vs $ timeout als sicheres $ apply

68

Ich versuche, die Nuancen der Verwendung des $ timeout-Dienstes in Angular als eine Art "sichere $ apply" -Methode besser zu verstehen. Grundsätzlich in Szenarien, in denen ein Code als Reaktion auf ein Angular-Ereignis oder ein Nicht-Angular-Ereignis wie jQuery oder ein Standard-DOM-Ereignis ausgeführt werden kann.

So wie ich die Dinge verstehe:

  1. Das Umschließen von Code in $ scope. $ Apply funktioniert gut in Szenarien, in denen Sie sich nicht bereits in einer Digest-Schleife befinden (auch bekannt als jQuery-Ereignis), aber einen Fehler auslösen, wenn ein Digest ausgeführt wird
  2. Das Umschließen von Code in einen $ timeout () -Aufruf ohne Verzögerungsparameter funktioniert unabhängig davon, ob er sich bereits in einem Digestzyklus befindet oder nicht

Wenn Sie sich den Angular-Quellcode ansehen, sieht es so aus, als würde $ timeout $ rootScope aufrufen. $ Apply ().

  1. Warum löst $ timeout () nicht auch einen Fehler aus, wenn bereits ein Digest-Zyklus ausgeführt wird?
  2. Ist es die beste Vorgehensweise, $ scope. $ Apply () zu verwenden, wenn Sie sicher sind, dass ein Digest noch nicht ausgeführt wird, und $ timeout (), wenn Sie es benötigen, um in beiden Fällen sicher zu sein?
  3. Ist $ timeout () wirklich eine akzeptable "sichere Anwendung" oder gibt es Fallstricke?

Vielen Dank für jeden Einblick.

bingles
quelle

Antworten:

63

Wenn Sie sich den Angular-Quellcode ansehen, sieht es so aus, als würde $ timeout $ rootScope aufrufen. $ Apply ().

  • Warum löst $ timeout () nicht auch einen Fehler aus, wenn bereits ein Digest-Zyklus ausgeführt wird?

$timeoutnutzt einen undokumentierten Angular-Dienst $browser. Insbesondere wird verwendet, $browser.defer()dass die Ausführung Ihrer Funktion asynchron über verzögert wird window.setTimeout(fn, delay), was immer außerhalb des Angular-Lebenszyklus ausgeführt wird. Nur einmal window.setTimeouthat gefeuert Ihre Funktion $timeoutaufrufen $rootScope.$apply().

  • Ist es die beste Vorgehensweise, $ scope. $ Apply () zu verwenden, wenn Sie sicher sind, dass ein Digest noch nicht ausgeführt wird, und $ timeout (), wenn Sie es benötigen, um in beiden Fällen sicher zu sein?

So würde ich sagen. Ein weiterer Anwendungsfall ist, dass Sie manchmal auf eine $ scope-Variable zugreifen müssen, von der Sie wissen, dass sie erst nach dem Digest initialisiert wird. Ein einfaches Beispiel wäre, wenn Sie den Status eines Formulars in Ihrem Controller-Konstruktor auf schmutzig setzen möchten (aus welchem ​​Grund auch immer). Ohne $ timeout wurde das FormControllernicht initialisiert und im $ scope veröffentlicht. Wenn Sie also $scope.yourform.setDirty()$ timeout einschließen, FormControllerwird sichergestellt, dass es initialisiert wurde. Sicher können Sie all dies mit einer Direktive ohne $ timeout tun, indem Sie nur ein anderes Anwendungsfallbeispiel angeben.

  • Ist $ timeout () wirklich eine akzeptable "sichere Anwendung" oder gibt es Fallstricke?

Es sollte immer sicher sein, aber Ihre Go-to-Methode sollte meiner Meinung nach immer auf $ apply () abzielen. Die aktuelle Angular-App, an der ich arbeite, ist ziemlich groß und wir mussten uns nur einmal auf $ timeout anstatt auf $ apply () verlassen.

Beyers
quelle
In meinem Projekt habe ich ein Szenario, in dem ich den $ scope. $ Digest () erhöhen muss, um die Ereignisse zu erfassen, die außerhalb von Angulars Sicht aufgetreten sind. Ist Timeout immer noch die Norm für safeApply oder safeDigest oder wurde dies im Framework intelligent behoben?
TrueBlue
1
Warum mussten Sie sich verlassen $timeoutanstatt $apply? Wenn Sie keinen Code freigeben können, können Sie zumindest den Grund dafür diskutieren?
Trysis
14

Wenn wir $ apply in der Anwendung stark verwenden, wird möglicherweise der Fehler: $ Digest bereits ausgeführt. Dies liegt daran, dass jeweils ein $ Digest-Zyklus ausgeführt werden kann. Wir können es durch $ timeout oder $ evalAsync beheben.

Das $ timeout generiert keinen Fehler wie "$ Digest ist bereits in Bearbeitung", da $ timeout Angular mitteilt, dass nach dem aktuellen Zyklus ein Timeout wartet und auf diese Weise sichergestellt wird, dass keine Kollisionen zwischen den Digest-Zyklen und damit der Ausgabe von $ auftreten Das Timeout wird in einem neuen $ Digest-Zyklus ausgeführt.

Ich habe versucht, sie zu erklären unter: Vergleich von Apply, Timeout, Digest und EvalAsync .

Vielleicht hilft es Ihnen.

Rahul Garg
quelle
Danke Rahul, Artikel ist interessant. Was mir jedoch fehlte, war eine Empfehlung, welche oder welche wann verwendet werden sollte. Danke noch einmal.
Matty J
1
Danke Matty für die Eingabe. Meiner Meinung nach ist $ evalAsync eine bessere Option als die verfügbare.
Rahul Garg
4

Soweit ich es verstehe, $timeouthandelt es sich um einen Wrapper, um setTimeoutden implizit aufgerufen wird $scope.$apply, dh er läuft außerhalb des Winkellebenszyklus, startet jedoch den Winkellebenszyklus selbst. Der einzige „Gotcha“ kann ich mir vorstellen, dass , wenn Sie erwarten , Ihr Ergebnis verfügbar sein dies $digest , Sie müssen einen anderen Weg finden , um „sichere Anwendung“ (was AFAIK, nur über verfügbar ist $scope.$$phase).

Jeff Hubbard
quelle