コントローラーまたはサービス内でコードを実行する場合、コードはAngularコンテキスト内で実行されるため、$ apply()を呼び出すことを心配する必要はありません。 これは、Angularがコードが実行中であることを理解しており、作業の完了後にダーティチェック(ダーティチェック)を実行することを意味します。 ディレクティブ内にいるとき、Angularの世界観はもう少し制限されています。 ディレクティブは、プレゼンテーションモデル(つまり、$スコープ)の変更についてAngularに通知する必要がある場合、$ apply()の呼び出し(または$ apply()の$ timeoutなどの呼び出し)を処理する必要があります。 ただし、ディレクティブのいくつかの側面は、Angularのコンテキスト内で実際に実行されるため、これを行うタイミングを決定するのは少し難しくなります。
すでに独自のディレクティブを作成している場合、2つのメッセージのいずれかを見たことは間違いありません。
$apply is already in progress.
$digest is already in progress.
これらのエラーメッセージは、Angularが既に知っているコードについてAngularに伝えようとしていることを示しています。 せいぜい、コンソールでこのエラーを見るだけです。 最悪の場合、ブラウザを壊す再帰的な失敗があります(Firebugのいくつかの欠点の1つ)。
ほとんどの場合、ディレクティブ内にいたためにこのエラーが発生し、ディレクティブ内で発生した$スコープのすべての変更についてAngularに通知しようとしました。 そして、あなたの行動の哲学は正しいものの、Angularがすでに監視している指令の一部があります。 特に、Angularはすでに次のことを知っています。
-バインディング機能
-属性の$ observe()ハンドラー
-$スコープの$ watch()のハンドラー
ミドルウェア関数で同期的に実行されるコード内、または$ observe()および$ watch()ハンドラー内で非同期に実行されるコード内で$スコープを変更する場合、アクションは既にAngularコンテキストで実行されます。 これは、Angularがコードの実行後にダーティチェックを行い、必要に応じて追加の$ダイジェストループを呼び出すことを意味します。
これを示すために、イメージの読み込みイベントを追跡する小さなディレクティブを作成しました。 ディレクティブの実行中にイメージが既にロードされている場合、Angularのコンテキストでハンドラーがすぐに呼び出されます。 画像がまだロードされていない場合、ハンドラーは非同期で呼び出され、Angularに変更を明示的に通知する必要があります。
注:デモ目的で、$ observe()および$ watch()ハンドラーが示されています。 彼らは何もしません。
サンプルコード <!doctype html> <html ng-app="Demo"> <head> <meta charset="utf-8" /> </head> <body> <ul ng-controller="ListController"> <li ng-repeat="image in images"> <p>Loaded: {{ image.complete }}</p> <img ng-src="{{ image.source }}" bn-load="imageLoaded( image )" /> </li> <li> <p>Loaded: {{ staticImage.complete }}</p> <img src="4.png" bn-load="imageLoaded( staticImage )" /> </li> </ul> <script type="text/javascript" src="//code.jquery.com/jquery-1.9.0.min.js"></script> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script> <script type="text/javascript"> </script> </body> </html>
ご覧のとおり、3つのイメージ(ngRepeat内)が動的にロードされ、1つが静的にロードされます。 4つのイメージすべての読み込みは、bnLoadディレクティブを使用して監視され、ライフサイクルのフェーズがログに記録されます。
静的画像-4.png-は、ディレクティブが実行されるまでにロードされ、最初のハンドラーがトリガーされ、コンソールに以下が表示されます。
handleLoad-同期:$適用
$ observe:4.png:$ダイジェスト
$ watch:true:$ダイジェスト
$ observe:1.png:$ダイジェスト
$ watch:false:$ダイジェスト
$ observe:2.png:$ダイジェスト
$ watch:false:$ダイジェスト
$ observe:3.png:$ダイジェスト
$ watch:false:$ダイジェスト
$ observe:1.png:$ダイジェスト
$ observe:2.png:$ダイジェスト
$ observe:3.png:$ダイジェスト
handleLoad-非同期:null
handleLoad-同期:$適用
$ watch:true:$ダイジェスト
handleLoad-非同期:null
handleLoad-同期:$適用
$ watch:true:$ダイジェスト
handleLoad-非同期:null
handleLoad-同期:$適用
$ watch:true:$ダイジェスト
どちらの側にアプローチするかが明確ではないことを知っているので、いくつかの重要なポイントを示します。
handleLoadSync()の最初のトリガーは、静的イメージが原因でした。 Angularがダーティチェックを実行する$適用フェーズ内で既に発生していることに注意してください。 これは、Angularのコンテキストに既にあるリンク関数link()で呼び出されたためです。
すべての$ observe()および$ watch()ハンドラーは、Angularのダーティライフサイクルチェックの$ダイジェストフェーズ内にあります。 一度、Angularに$スコープの変更について伝える必要はありません。 Angularは、$ダイジェストサイクルごとにダーティチェックを自動的に実行します。
非同期的にロードされたすべての画像は、handleLoadAsync()メソッドを呼び出します。このメソッドは、$ apply()メソッドを使用してこれらの変更についてAngularに通知します。 そのため、handleLoadSync()の後続のすべてのメソッドは$ applyフェーズにあります-これらはhandleLoadAsync()ハンドラーから呼び出されました。
前述のように、Angularのディレクティブは非常に強力ですが、サブツイストを使用すると、頭を所定の位置に置くために息を吹き込む必要があります。 Angularのディレクティブのランタイムはその機能にとって重要であり、これは正しく理解するのが最も難しいことの1つです。 ブラウザからの部分的なサポートしか持たないCSSトランジションなどを追加すると、$ダイジェストの問題が1つのブラウザで発生し、別のブラウザでは発生しないことがすぐにわかります。 この研究がこのような問題の原因をよりよく理解するのに役立つことを願っています。
翻訳者のメモ: Angularのライフサイクルのフェーズを示すライブ例