angularでアプリケーションを構築して以来、自分のページをスコープに合わせて手動で更新する必要があることがわかりました。
$apply()
これを行う唯一の方法は、コントローラーとディレクティブのスコープから呼び出すことです。これに関する問題は、次のようなエラーをコンソールにスローし続けることです。
エラー: $digest は既に進行中です
このエラーを回避する方法、または同じことを別の方法で達成する方法を知っている人はいますか?
angularでアプリケーションを構築して以来、自分のページをスコープに合わせて手動で更新する必要があることがわかりました。
$apply()
これを行う唯一の方法は、コントローラーとディレクティブのスコープから呼び出すことです。これに関する問題は、次のようなエラーをコンソールにスローし続けることです。
エラー: $digest は既に進行中です
このエラーを回避する方法、または同じことを別の方法で達成する方法を知っている人はいますか?
このトピックに関する 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 を書いている人たちからの答えなので、私にとっては十分です!
このパターンを使用しないでください-これにより、解決するよりも多くのエラーが発生します。何かが修正されたと思っていても、修正されませんでした。
をチェックすることで、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による
- しないでください
if (!$scope.$$phase) $scope.$apply()
、それはあなた$scope.$apply()
がコールスタックで十分に高くないことを意味します。
ダイジェスト サイクルは同期呼び出しです。完了するまで、ブラウザのイベント ループに制御を渡しません。これに対処するにはいくつかの方法があります。これに対処する最も簡単な方法は、組み込みの $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 が終了することが保証されます。
ここでの回答の多くには適切なアドバイスが含まれていますが、混乱を招く可能性もあります。単に使用する$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 を使用することをお勧めします。
新しい強力なメソッドが $scope: に追加されました$evalAsync
。基本的に、コールバックが発生している場合は、現在のダイジェスト サイクル内でコールバックを実行します。それ以外の場合は、新しいダイジェスト サイクルがコールバックの実行を開始します。
これは$scope.$digest
、HTML の分離された部分のみを同期する必要があることを本当に知っている場合 ($apply
何も進行していない場合に新しいものがトリガーされるため) ほど良くはありませんが、関数を実行している場合はこれが最適なソリューションです。たとえば、キャッシュされている可能性のあるリソースをフェッチした後など、同期的に実行されるかどうかはわかりません。サーバーへの非同期呼び出しが必要になる場合があり、そうでない場合、リソースはローカルで同期的にフェッチされます。
これらの場合、および があった他のすべての場合は、!$scope.$$phase
必ず使用してください$scope.$evalAsync( callback )
たとえば 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();
}]
)
このエラーが発生した場合、基本的には、ビューが既に更新中であることを意味します。$apply()
コントローラー内で呼び出す必要はありません。ビューが期待どおりに更新されず、 を呼び出した後にこのエラーが発生$apply()
する場合は、モデルを正しく更新していない可能性が高いです。詳細を投稿していただければ、核となる問題を突き止めることができます。
この方法を使用すると、エラーが発生することがあります ( https://stackoverflow.com/a/12859093/801426 )。
これを試して:
if(! $rootScope.$root.$$phase) {
...
ダイジェスト サイクルをトリガーするのではなく、カスタム イベントを使用することをお勧めします。
カスタム イベントをブロードキャストし、このイベントのリスナーを登録することは、ダイジェスト サイクルにいるかどうかに関係なく、発生させたいアクションをトリガーするための優れたソリューションであることがわかりました。
カスタム イベントを作成すると、scope.$apply を呼び出した場合のように、そのイベントにサブスクライブされたリスナーのみをトリガーし、スコープにバインドされたすべてのウォッチをトリガーしないため、コードの効率も向上します。
$scope.$on('customEventName', function (optionalCustomEventArguments) {
//TODO: Respond to event
});
$scope.$broadcast('customEventName', optionalCustomEventArguments);
yearofmoo は、再利用可能な $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);
関数が実行されることがわかっている場所では$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 を実行するだけで十分です。
$scope.$$phase || $scope.$apply();
代わりに使用
私はこの方法を使用してきましたが、完全にうまく機能しているようです。これは、サイクルが終了するまで待ってからトリガーします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);
}
}
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
インスペクション メソッドも使用しています。
debugger を無効にすると、エラーは発生しなくなりました。私の場合、デバッガーがコードの実行を停止したことが原因でした。
上記の回答に似ていますが、これは私にとって忠実に機能しました...サービスで追加:
//sometimes you need to refresh scope, use this to prevent conflict
this.applyAsNeeded = function (scope) {
if (!scope.$$phase) {
scope.$apply();
}
};