if(!$ scope。$$ phase)$ scope。$ apply()をアンチパターンで使用するのはなぜですか?


92

$scope.$applyコードで使用する必要がある場合と、「ダイジェストがすでに進行中」のエラーがスローされる場合があります。だから私はこれを回避する方法を見つけ始め、この質問を見つけました:AngularJS:$ scope。$ apply()を呼び出すときにすでに進行中のエラー$ digestを防ぎます。ただし、コメント(およびAngular Wiki)では、以下を読むことができます。

(!$ scope。$$ phase)$ scope。$ apply()の場合は行わないでください。これは、$ scope。$ apply()がコールスタックで十分に高くないことを意味します。

だから今私は2つの質問があります:

  1. なぜこれがアンチパターンなのですか?
  2. $ scope。$ applyを安全に使用するにはどうすればよいですか?

「ダイジェストはすでに進行中」エラーを防ぐための別の「解決策」は$ timeoutを使用しているようです:

$timeout(function() {
  //...
});

それは行く方法ですか?安全ですか?だからここに本当の質問があります:「すでに進行中のダイジェスト」エラーの可能性を完全に排除するにはどうすればよいですか?

PS:同期していない非Angularjsコールバックでのみ$ scope。$ applyを使用しています。(私が知る限り、変更を適用したい場合は$ scope。$ applyを使用する必要がある状況です)


私の経験から、scope角の内側から操作するのか角の外側から操作するのかは常に知っているはずです。したがって、これに応じて、電話が必要かどうかを常に知ってscope.$applyいます。そして、角度scope操作と非角度操作の両方に同じコードを使用している場合、それは間違っています。常に分離する必要があります...したがって、基本的に、チェックが必要なケースに遭遇した場合scope.$$phase、コードは正しい方法で設計されており、それを「正しい方法」で実行する方法が常にあります
doodeec

1
私はこれを非角度のコールバックでのみ使用しています(!)これが私が混乱している理由です
ドミニクゴルターマン

2
角度でない場合、digest already in progressエラーは発生しません
doodeec 14

1
私もそう思っていました。問題は、常にエラーをスローするとは限らないことです。たまにしか。私の疑いは、適用が別のダイジェストとBY CHANCEと衝突することです。それは可能ですか?
ドミニクゴルターマン2014年

コールバックが厳密に角張っていない場合、それは可能ではないと思います
doodeec

回答:


113

さらに掘り下げた後、常に安全に使用できるかどうかの質問を解決できました$scope.$apply。短い答えはイエスです。

長い答え:

ブラウザがJavaScriptを実行する方法により、2つのダイジェスト呼び出しが偶然に衝突することはありません。

私たちが記述したJavaScriptコードは、一度にすべてが実行されるのではなく、順番に実行されます。これらの各ターンは、最初から最後まで中断されることなく実行されます。ターンが実行されているとき、ブラウザーでは他に何も起こりません。(http://jimhoskins.com/2012/12/17/angularjs-and-apply.htmlから)

したがって、「ダイジェストはすでに進行中です」というエラーは1つの状況でのみ発生する可能性があります。$ applyが別の$ apply内で発行された場合。

$scope.apply(function() {
  // some code...
  $scope.apply(function() { ... });
});

たとえばのコールバックなど、純粋な非Angularjsコールバックで$ scope.applyを使用する場合、この状況発生しません。したがって、次のコードは100%防弾であり、実行する必要はありませんsetTimeoutif (!$scope.$$phase) $scope.$apply()

setTimeout(function () {
    $scope.$apply(function () {
        $scope.message = "Timeout called!";
    });
}, 2000);

これでも安全です:

$scope.$apply(function () {
    setTimeout(function () {
        $scope.$apply(function () {
            $scope.message = "Timeout called!";
        });
    }, 2000);
});

安全ではないもの(すべてのangularjsヘルパーと同様、$ timeoutがすでに呼び出している$scope.$applyため):

$timeout(function () {
    $scope.$apply(function () {
        $scope.message = "Timeout called!";
    });
}, 2000);

これは、の使用がif (!$scope.$$phase) $scope.$apply()アンチパターンである理由も説明しています。たとえば、次の$scope.$applyようsetTimeoutに純粋なjsコールバックで、正しい方法で使用する場合は必要ありません。

詳細については、http://jimhoskins.com/2012/12/17/angularjs-and-apply.htmlをご覧ください。


私は私がサービスを作成する例だ $document.bind('keydown', function(e) { $rootScope.$apply(function() { // a passed through function from the controller gets executed here }); });私は$を行う必要があり、ここで適用されますなぜ私は$ document.bindを使用していますので、本当に、知りません..私を
ベティセント

$ documentは「ブラウザのwindow.documentオブジェクトのjQueryまたはjqLit​​eラッパー」にすぎないためです。次のように実装されます:function $DocumentProvider(){ this.$get = ['$window', function(window){ return jqLite(window.document); }]; }そこには適用されません。
ドミニクゴルターマン2014年

11
$timeout意味的には、遅延後にコードを実行することを意味します。機能的に安全な方法かもしれませんが、ハックです。$digestサイクルが進行中であるのか、または既にの内部にいるのかわからない場合に、$ applyを使用する安全な方法があるはずです$apply
John Strickler、2014年

1
もう1つの理由:パブリックAPIの一部ではない内部変数($$ phase)を使用しているため、新しいバージョンのangularで変更され、コードが壊れる可能性があります。同期イベントのトリガーに関する問題は興味深いものです
Dominik

4
新しいアプローチは、可能であれば現在のダイジェストサイクルまたは次のサイクルで安全に実行される$ scope。$ evalAsync()を使用することです。bennadel.com/blog/…を
jaymjarri

16

それは間違いなく今はアンチパターンです。$$ phaseをチェックしても、ダイジェストが爆発するのを見ました。$$プレフィックスで示される内部APIにアクセスすることは想定されていません。

あなたは使うべきです

 $scope.$evalAsync();

これはAngular ^ 1.4で推奨されるメソッドであり、アプリケーション層のAPIとして具体的に公開されているためです。


9

どのような場合でも、ダイジェストが進行中で、別のサービスをダイジェストにプッシュすると、エラーが発生します。つまり、ダイジェストはすでに進行中です。これを治すには2つのオプションがあります。ポーリングなど、進行中の他のダイジェストを確認できます。

最初の1つ

if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
    $scope.$apply();
}

上記の条件に該当する場合は、$ scope。$ apply otherwiesを適用できません。

2番目の解決策は$ timeoutを使用することです

$timeout(function() {
  //...
})

$ timeoutが実行を完了するまで、他のダイジェストを開始させません。


1
反対票を投じた; この質問では、ここで説明していることを行わない理由を具体的に尋ねています。いつ使用するかについては、@ gaulの優れた回答を参照してください$scope.$apply();
PureSpider 2014年

質問には答えません$timeoutが、鍵です!それは動作し、後で私はそれも推奨されていることがわかりました。
Himel Nag Rana、2015

この2年後にコメントを追加するのはかなり遅いですが、$ timeoutを使いすぎると、アプリケーションの構造が適切でないとパフォーマンスが高くなりすぎる可能性があるので注意してください
cpoDesign

9

scope.$apply$digest双方向データバインディングの基本となるサイクルをトリガーします

$digestオブジェクトの周期チェックは、モデルは(正確には、すなわち$watch)に取り付けられ$scope、それらの値が変更され、それは変化を検出した場合、それは、ビューを更新するために必要な措置をとるかどうかを評価します。

これを使用$scope.$applyすると、「Already in progress」というエラーが発生するので、$ digestが実行されていることは明らかですが、何がトリガーになりましたか?

ans->すべての$http呼び出し、すべてのng-click、repeat、show、hideなどが$digestサイクルをトリガーし、すべての$ SCOPEで実行される最悪の部分。

つまり、ページに4つのコントローラーまたはディレクティブA、B、C、Dがあるとします

$scopeそれぞれに4つのプロパティがある場合、ページには合計16の$ scopeプロパティがあります。

$scope.$applyコントローラーDでトリガーすると、$digestサイクルが16の値すべてをチェックします!!! さらに、すべての$ rootScopeプロパティ。

Answer->しかし$scope.$digest$digest子と同じスコープでトリガーされるため、4つのプロパティのみがチェックされます。したがって、Dの変更がA、B、Cに影響しないことが確実な場合は、$scope.$digest notを使用してください$scope.$apply

したがって、$digestユーザーがイベントを発生させいない場合でも、単なるng-clickまたはng-show / hideが100以上のプロパティでサイクルをトリガーしている可能性があります。


2
ええ、残念ながら、プロジェクトの後半にこのことに気づきました。これを最初から知っていたら、Angularを使用しなかったでしょう。すべての標準ディレクティブは$ scope。$ applyを起動し、次に$ scope。$ applyが$ rootScope。$ digestを呼び出し、すべてのスコープに対してダーティチェックを実行します。あなたが私に尋ねるならば、貧弱なデザイン決定。データがこれらのスコープにリンクされている方法を知っているので、どのスコープをダーティチェックするかを制御できます。
MoonStom

0

を使用して$timeout、それが推奨される方法です。

私のシナリオでは、WebSocketから受け取ったデータに基づいてページ上のアイテムを変更する必要があります。そして、それは$ timeoutなしでAngularの外にあるので、唯一のモデルは変更されますがビューは変更されません。Angularはデータの一部が変更されたことを知らないからです。$timeout基本的には、Angularに$ digestの次のラウンドで変更を行うように指示しています。

以下も試してみましたがうまくいきました。私との違いは、$ timeoutがより明確になることです。

setTimeout(function(){
    $scope.$apply(function(){
        // changes
    });
},0)

ソケットコードを$ applyでラップする方がはるかにクリーンです(AJAXコードでのAngularのように、つまり$http)。それ以外の場合は、このコードをすべての場所で繰り返す必要があります。
2015

これは絶対にお勧めできません。また、$ scopeに$$ phaseがある場合、これを実行するとエラーが発生することがあります。代わりに、$ scope。$ evalAsync();を使用する必要があります。
FlavorScape

or $scope.$applyを使用している場合は必要ありませんsetTimeout$timeout
Kunal

-1

私は非常にクールな解決策を見つけました:

.factory('safeApply', [function($rootScope) {
    return function($scope, fn) {
        var phase = $scope.$root.$$phase;
        if (phase == '$apply' || phase == '$digest') {
            if (fn) {
                $scope.$eval(fn);
            }
        } else {
            if (fn) {
                $scope.$apply(fn);
            } else {
                $scope.$apply();
            }
        }
    }
}])

必要な場所に注入します。

.controller('MyCtrl', ['$scope', 'safeApply',
    function($scope, safeApply) {
        safeApply($scope); // no function passed in
        safeApply($scope, function() { // passing a function in
        });
    }
])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.