858

angularでアプリケーションを構築して以来、自分のページをスコープに合わせて手動で更新する必要があることがわかりました。

$apply()これを行う唯一の方法は、コントローラーとディレクティブのスコープから呼び出すことです。これに関する問題は、次のようなエラーをコンソールにスローし続けることです。

エラー: $digest は既に進行中です

このエラーを回避する方法、または同じことを別の方法で達成する方法を知っている人はいますか?

4

28 に答える 28

675

このトピックに関する Angular 関係者との最近の議論から:将来を見据えた理由から、使用しないでください。$$phase

それを行う「正しい」方法を求められたとき、答えは現在

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

私は最近、フェイスブック、グーグル、およびツイッター API をラップする角度サービスを作成しているときに、さまざまな程度でコールバックが渡されることに遭遇しました。

サービス内の例を次に示します。(簡潔にするために、残りのサービス (変数の設定、$timeout の挿入など) は省略されています。)

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);
            }
        });
    });
});

$timeout の遅延引数はオプションであり、設定されていない場合はデフォルトで 0 になることに注意してください ( $timeoutは$browser.deferを呼び出し、遅延が設定されていない場合はデフォルトで 0になります) 。

少し直感的ではありませんが、それが Angular を書いている人たちからの答えなので、私にとっては十分です!

于 2013-09-25T04:06:19.560 に答える
671

このパターンを使用しないでください-これにより、解決するよりも多くのエラーが発生します。何かが修正されたと思っていても、修正されませんでした。

をチェックすることで、a$digestがすでに進行中であるかどうかを確認でき$scope.$$phaseます。

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

$scope.$$phase"$digest"またはが進行中の"$apply"場合は戻ります。これらの状態の違いは、現在のスコープとその子のウォッチを処理し、すべてのスコープのウォッチャーを処理することだと思います。$digest$apply$digest$apply

@ dnc253の要点として、自分が電話をかけている、$digestまたは$apply頻繁に電話をかけていることに気付いた場合は、間違っている可能性があります。Angularの範囲外でDOMイベントが発生した結果、スコープの状態を更新する必要がある場合は、通常、ダイジェストする必要があります。たとえば、Twitterのブートストラップモーダルが非表示になった場合です。DOMイベント$digestは、進行中の場合に発生する場合と発生しない場合があります。そのため、このチェックを使用します。

誰かが知っているなら、もっと良い方法を知りたいです。


コメントから:@anddoutoiによる

angle.jsアンチパターン

  1. しないでくださいif (!$scope.$$phase) $scope.$apply()、それはあなた$scope.$apply()がコールスタックで十分に高くないことを意味します。
于 2012-10-12T12:28:54.100 に答える
332

ダイジェスト サイクルは同期呼び出しです。完了するまで、ブラウザのイベント ループに制御を渡しません。これに対処するにはいくつかの方法があります。これに対処する最も簡単な方法は、組み込みの $timeout を使用することです。2 つ目の方法は、アンダースコアまたはロダッシュを使用している場合 (そうすべきです)、次のように呼び出します。

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

または、lodash がある場合:

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

いくつかの回避策を試しましたが、すべてのコントローラー、ディレクティブ、さらには一部のファクトリに $rootScope を挿入することを嫌いました。そのため、これまでのところ、$timeout と _.defer が私たちのお気に入りです。これらのメソッドは、Angular に次のアニメーション ループまで待機するように指示します。これにより、現在の scope.$apply が終了することが保証されます。

于 2013-07-30T22:51:27.353 に答える
270

ここでの回答の多くには適切なアドバイスが含まれていますが、混乱を招く可能性もあります。単に使用する$timeoutことは、最良の解決策でも適切な解決策でもありません。また、パフォーマンスやスケーラビリティが気になる方は必ずお読みください。

知っておくべきこと

  • $$phaseフレームワーク専用であり、それには正当な理由があります。

  • $timeout(callback)現在のダイジェスト サイクル (存在する場合) が完了するまで待機し、コールバックを実行し、最後にフルを実行します$apply

  • $timeout(callback, delay, false)同じことを行います (コールバックを実行する前にオプションの遅延を使用) が$apply、Angular モデル ($scope) を変更しなかった場合にパフォーマンスを節約する (3 番目の引数) を起動しません。

  • $scope.$apply(callback)$rootScope.$digestこれは、アプリケーションのルート スコープとそのすべての子を、分離されたスコープ内にいる場合でも再消化することを意味します。

  • $scope.$digest()モデルをビューに単純に同期しますが、親スコープを消化しません。これにより、(主にディレクティブから) 分離されたスコープを使用して HTML の分離された部分で作業するときに多くのパフォーマンスを節約できます。$digest はコールバックを取りません: コードを実行してからダイジェストします。

  • $scope.$evalAsync(callback)angularjs 1.2 で導入され、おそらくほとんどの問題を解決します。詳細については、最後の段落を参照してください。

  • が表示された場合は$digest already in progress error、アーキテクチャが間違っています。スコープを再消化する必要がないか、それを担当するべきではありません(以下を参照)。

コードを構造化する方法

そのエラーが発生した場合は、スコープが既に進行しているときにスコープを消化しようとしています。その時点でのスコープの状態がわからないため、その消化を処理する責任はありません。

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);
});

また、大きな Angular アプリケーションの一部でありながら、分離された小さなディレクティブに取り組んでいることがわかっている場合は、パフォーマンスを節約するために $apply ではなく $digest を使用することをお勧めします。

Angularjs 1.2 以降の更新

新しい強力なメソッドが $scope: に追加されました$evalAsync。基本的に、コールバックが発生している場合は、現在のダイジェスト サイクル内でコールバックを実行します。それ以外の場合は、新しいダイジェスト サイクルがコールバックの実行を開始します。

これは$scope.$digest、HTML の分離された部分のみを同期する必要があることを本当に知っている場合 ($apply何も進行していない場合に新しいものがトリガーされるため) ほど良くはありませんが、関数を実行している場合はこれが最適なソリューションです。たとえば、キャッシュされている可能性のあるリソースをフェッチした後など、同期的に実行されるかどうかはわかりません。サーバーへの非同期呼び出しが必要になる場合があり、そうでない場合、リソースはローカルで同期的にフェッチされます。

これらの場合、および があった他のすべての場合は、!$scope.$$phase必ず使用してください$scope.$evalAsync( callback )

于 2014-04-16T06:59:44.770 に答える
33

たとえば CodeMirror や Krpano などのサードパーティのスクリプトで同じ問題が発生しましたが、ここで説明した safeApply メソッドを使用してもエラーは解決しませんでした。

しかし、それを解決したのは $timeout サービスを使用することです (最初に注入することを忘れないでください)。

したがって、次のようなものです。

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

コード内で使用している場合

これ

おそらく、それがファクトリ ディレクティブのコントローラー内にあるか、何らかのバインディングが必要なだけの場合は、次のようにします。

.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();

  }]
)
于 2013-09-03T00:15:39.200 に答える
28

このエラーが発生した場合、基本的には、ビューが既に更新中であることを意味します。$apply()コントローラー内で呼び出す必要はありません。ビューが期待どおりに更新されず、 を呼び出した後にこのエラーが発生$apply()する場合は、モデルを正しく更新していない可能性が高いです。詳細を投稿していただければ、核となる問題を突き止めることができます。

于 2012-10-04T14:41:41.483 に答える
9

この方法を使用すると、エラーが発生することがあります ( https://stackoverflow.com/a/12859093/801426 )。

これを試して:

if(! $rootScope.$root.$$phase) {
...
于 2013-04-30T13:24:50.043 に答える
4

ダイジェスト サイクルをトリガーするのではなく、カスタム イベントを使用することをお勧めします。

カスタム イベントをブロードキャストし、このイベントのリスナーを登録することは、ダイジェスト サイクルにいるかどうかに関係なく、発生させたいアクションをトリガーするための優れたソリューションであることがわかりました。

カスタム イベントを作成すると、scope.$apply を呼び出した場合のように、そのイベントにサブスクライブされたリスナーのみをトリガーし、スコープにバインドされたすべてのウォッチをトリガーしないため、コードの効率も向上します。

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


$scope.$broadcast('customEventName', optionalCustomEventArguments);
于 2014-01-02T15:26:46.030 に答える
3

yearofmoo は、再利用可能な $safeApply 関数を作成してくれました。

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

使用法 :

//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);
于 2013-11-21T13:33:42.697 に答える
2

関数が実行されることがわかっている場所では$evalなく、呼び出すことでこの問題を解決できました。$apply$digest

docsによると、$apply基本的にこれを行います:

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

私の場合、ng-clickはスコープ内の変数を変更し、その変数の $watch は、 でなければならない他の変数を変更します$applied。この最後の手順により、「ダイジェストは既に進行中です」というエラーが発生します。

ウォッチ式内で置き換えることにより、スコープ変数が期待どおりに更新され$applyます。$eval

したがって、Angular 内の他の変更のためにダイジェストが実行される場合は、'$eval ing を実行するだけで十分です。

于 2013-09-01T21:20:02.743 に答える
2

$scope.$$phase || $scope.$apply();代わりに使用

于 2015-05-21T11:27:43.330 に答える
1

私はこの方法を使用してきましたが、完全にうまく機能しているようです。これは、サイクルが終了するまで待ってからトリガーしますapply()。必要な場所から関数を呼び出すだけapply(<your scope>)です。

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);
  }
}
于 2016-05-10T14:23:03.373 に答える
1

Angular ドキュメントがアンチパターン$$phaseのチェックを呼び出すことを理解して、取得して動作させようとしました。$timeout_.defer

timeout および deferred メソッドは、 FOUT{{myVar}}のように domに解析されていないコンテンツのフラッシュを作成します。私にとって、これは受け入れられませんでした。何かがハックであり、適切な代替手段がないことを独断的に言われることはあまりありません。

毎回機能する唯一のものは次のとおりです。

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

この方法の危険性、またはコメントやAngularチームでハックとして説明されている理由がわかりません。コマンドは正確で読みやすいようです。

「まだダイジェストが行われていない限り、ダイジェストを行います」

CoffeeScript では、さらにきれいです。

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

これの何が問題なのですか?FOUTを作成しない代替手段はありますか? $safeApplyは問題ないように見えますが、$$phaseインスペクション メソッドも使用しています。

于 2013-12-31T19:29:23.060 に答える
1

debugger を無効にすると、エラーは発生しなくなりました。私の場合、デバッガーがコードの実行を停止したことが原因でした。

于 2019-09-02T18:50:58.033 に答える
0

上記の回答に似ていますが、これは私にとって忠実に機能しました...サービスで追加:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };
于 2016-01-12T20:39:08.413 に答える