AngularJSで$ scope。$ watchと$ scope。$ applyを使用するにはどうすればよいですか?


1088

私が使用方法を理解していない$scope.$watch$scope.$apply。公式ドキュメントは役に立ちません。

私が具体的に理解していないこと:

  • それらはDOMに接続されていますか?
  • モデルのDOM変更を更新するにはどうすればよいですか?
  • それらの間の接続点は何ですか?

私はこのチュートリアルを試しましたが、それは理解$watch$apply当たり前のことです。

何をし$apply、何を$watchし、どのように適切に使用しますか?

回答:


1737

AngularJSを理解するには、AngularJSがどのように機能するかを知っておく必要があります。

ダイジェストサイクルと$ scope

何よりもまず、AngularJSはいわゆるダイジェストサイクルの概念を定義しています。このサイクルはループと見なすことができ、その間、AngularJSはすべてのsが監視するすべての変数に変更があるかどうかをチェックします$scope。したがって$scope.myVar、コントローラーで定義し、この変数が監視対象としてマークされている場合、AngularJSにmyVarループの各反復での変更を監視するように暗黙的に指示しています。

自然なフォローアップの質問は次のようになり$scopeます。幸い、そうではありません。内のすべてのオブジェクトへの変更を監視する場合$scope、ダイジェストループをすぐに評価するには時間がかかり、パフォーマンスの問題がすぐに発生します。そのため、AngularJSチームは、いくつかの$scope変数を監視対象として宣言する2つの方法を提供しました(以下を参照)。

$ watchは$ scopeの変更を監視するのに役立ちます

$scope監視対象として変数を宣言するには、2つの方法があります。

  1. 式を介してテンプレートで使用する <span>{{myVar}}</span>
  2. $watchサービスを介して手動で追加する

広告1)これは最も一般的なシナリオであり、以前に見たことがあると思いますが、これがバックグラウンドで時計を作成したことを知りませんでした。はい、ありました!AngularJSディレクティブ(などng-repeat)を使用しても、暗黙の監視を作成できます。

広告2)これは、独自の時計を作成する方法です。$watchサービスは、に関連付けられている値$scopeが変更されたときに、コードを実行するのに役立ちます。ほとんど使用されませんが、役立つ場合があります。たとえば、 'myVar'が変更されるたびにコードを実行する場合は、次のようにします。

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$ applyを使用すると、変更をダイジェストサイクルに統合できます

$apply機能は統合メカニズムと考えることができます。ご覧のとおり$scopeオブジェクトに直接アタッチされたウォッチ変数を変更するたびに、AngularJSは変更が行われたことを認識します。これは、AngularJSがすでにこれらの変更を監視することを知っていたためです。したがって、フレームワークで管理されているコードで発生した場合、ダイジェストサイクルが続行されます。

ただし、AngularJSの世界外で値変更して、変更が正常に伝播することを確認したい場合があります。これを検討してください$scope.myVar-jQueryの$.ajax()ハンドラー内で変更される値があります。これは将来のある時点で発生します。AngularJSはjQueryで待機するように指示されていないため、これが発生するのを待つことができません。

これに取り組むために、$apply導入されました。消化サイクルを明示的に開始できます。ただし、これを使用して一部のデータをAngularJS(他のフレームワークとの統合)に移行する必要がありますが、AngularJSがエラーをスローするため、このメソッドを通常のAngularJSコードと組み合わせて使用​​しないでください。

これらすべてはDOMとどのように関連していますか?

さて、これですべてを理解したので、本当にチュートリアルをもう一度実行する必要があります。ダイジェストサイクルは$scope、何も変更されない限り、すべてのに接続されたすべてのウォッチャーを評価することにより、UIとJavaScriptコードが同期されたままであることを確認します。ダイジェストループで変更が発生しなくなった場合は、終了したと見なされます。

$scopeオブジェクトは、コントローラーで明示的に、または{{expression}}ビューで直接フォームで宣言することにより、オブジェクトにアタッチできます。

これがすべてに関する基本的な知識を明確にするのに役立つことを願っています。

さらに読む:


57
「角度はすべての$ scopesにアタッチされたすべての変数に変更があるかどうかをチェックします」-私はそれが正しくないと思います。Angularのみ(ダーティー)が、$ watchesが設定されている$ scopeプロパティをチェックすると信じています(ビューで{{}}を使用すると$ watchが自動的に作成されることに注意してください)。スコープページの「スコープ$ watchパフォーマンスの考慮事項」のセクションも参照してください
Mark Rajcok 2013

5
それは事実かもしれません。私はそれについてもっと読んで私の答えを編集する時間を見つけるように努めます。
ŁukaszBachman

15
@MarkRajcok、あなたは正しかった。私は返信を変更し、これがどのように実装されているかをうまく示している記事を指摘しました。
ŁukaszBachman

3
これをどう使う?( "Control as"メソッド)
Leandro

2
「として制御」を使用しても、上記の情報に影響はありません。this.myVarを使用すると、myVarがスコープに配置されます。
MarcusRådell2014年

161

AngularJSでは、モデルを更新し、ビュー/テンプレートはDOMを「自動的に」(組み込みまたはカスタムのディレクティブを介して)更新します。

$ applyと$ watchはどちらもScopeメソッドであり、DOMとは関係ありません。

概念」ページ(「ランタイム」セクション)には、$ digestループ、$ apply、$ evalAsyncキュー、および$ watchリストについてのかなり良い説明があります。これはテキストに付随する画像です:

$ digestループ

スコープ(通常はコントローラーとディレクティブ(それらのリンク関数やそのコントローラー))にアクセスできるコードが何であれ、AngularJSがそのスコープに対して評価する「watchExpression」を設定できます。この評価は、AngularJSが$ digestループ(特に、「$ watch list」ループ)に入るたびに発生します。個々のスコーププロパティを監視したり、2つのプロパティを一緒に監視する関数を定義したり、配列の長さを監視したりできます。

「AngularJSの内部」で何かが発生した場合-たとえば、AngularJSの双方向データバインディングが有効になっている(つまり、ng-modelを使用する)テキストボックスに入力すると、$ httpコールバックが起動するなど– $ applyがすでに呼び出されているため、上の図の「AngularJS」長方形の中にあります。すべてのwatchExpressionsが評価されます(おそらく2回以上–変更が検出されなくなるまで)。

「AngularJSの外で」何かが発生した場合(たとえば、ディレクティブでbind()を使用した後、そのイベントが発生し、コールバックが呼び出されるか、jQueryで登録されたコールバックが発生します)、まだ「ネイティブ」の長方形にいます。コールバックコードが$ watchが監視しているものを変更する場合は、$ applyを呼び出してAngularJSの四角形に入り、$ digestループが実行されるようにします。これにより、AngularJSが変更に気付き、その魔法を実行します。


5
私は考えを理解していますが、私が理解していないのは、データが実際に転送される方法です。大量のデータを持つオブジェクトであるモデルがあります。その一部を使用してDOMを操作します。その後、一部が変更されます。変更したデータをモデルの適切な場所に配置するにはどうすればよいですか?私が使用した例では、彼は操作を行い、最終的には単にを使用scope.$apply(scope.model)していますが、どのデータが転送され、どのようにモデルの適切な場所に転送されるのか理解できません。
ilyo 2013

6
魔法のようなデータ転送は行われていません。通常、Angularアプリでは、Angularモデルを変更してから、ビュー/ DOMの更新を実行する必要があります。Angularの外部でDOMを更新する場合は、モデルを手動で更新する必要があります。 scope.$apply(scope.model)単にscope.modelAngular式として評価され、$ digestループに入ります。あなたが参照する記事scope.$apply()では、モデルがすでに$ watchされているので、おそらく十分でしょう。stop()関数がモデルを更新していて(toUpdateはscope.modelへの参照であると思います)、次に$ applyが呼び出されます。
Mark Rajcok 2013

AngularJSのドキュメントがこの回答の下からシフトアウトしているようです(最初のリンクには「ランタイム」または$watchページがなく、2番目のリンクは壊れています-現在のところ、とにかく)。悲しいことに、アーカイブバージョンは、コンテンツを作成した非同期プロセスをキャッシュしませんでした。
ルフィン

52

AngularJSはこのイベントループを拡張し、という名前を作成しますAngularJS context

$ watch()

UIで何かをバインドするたび$watchに、$watchリストにを挿入ます

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

ここにあります $scope.user、最初の入力に$scope.passバインドされていると、2番目の入力にバインドされているがあります。これにより、2つの$watchesが$watchリストに追加さます

私たちの場合はテンプレートがロードされ、AKAリンクフェーズでは、コンパイラは、すべてのディレクティブを探し、すべて作成します$watch必要とされているESを。

AngularJSは$watch$watchcollectionとを提供します$watch(true)。以下は、ウォッチャーから3つすべてを詳細に説明したわかりやすい図です。

ここに画像の説明を入力してください

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest ループ

ブラウザがAngularJSコンテキストで管理できるイベントを受け取ると、$digestループが発生します。このループは、2つの小さなループで構成されています。1つは$evalAsyncキューを処理し、もう1つはを処理し$watch listます。の$digestリストをループし$watchます

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

$watchng-clickはウォッチを作成しないため、ここには1つしかありません。

ボタンを押します。

  1. ブラウザがAngularJSコンテキストに入るイベントを受け取ります
  2. $digestループが実行され、変更ごとに$ウォッチを要求します。
  3. 以来$watch$ scope.nameの変化を見ていたが、変更されたことを報告し、それが別の強制します$digestループを。
  4. 新しいループは何も報告しません。
  5. ブラウザーはコントロールを取得し、$ scope.nameの新しい値を反映してDOMを更新します
  6. ここで重要なのは、AngularJSコンテキストに入るすべてのイベントが$digestループを実行することです。つまり、入力に文字を書き込むたびに、ループは$watchこのページのすべてをチェックして実行されます。

$ apply()

$applyイベントが発生したときに呼び出すと、角度のコンテキストを通過しますが、呼び出さない場合は、イベントの外側で実行されます。それはそれと同じくらい簡単です。$apply呼び出します$digest()ループを内部的にすべての監視を反復処理して、DOMが新しく更新された値で更新されるようにします。

$apply()この方法は、全体にウォッチャをトリガする$scopeのに対し、チェーン$digest()方式のみ電流にウォッチャをトリガする$scopechildren上位の$scopeオブジェクトがローカルの変更について知る必要がない場合は、を使用できます$digest()


18

私はカバー非常に綿密な動画を見つけ$watch$apply$digestとにサイクルをダイジェスト:

以下は、概念を説明するためにこれらのビデオで使用されている2つのスライドです(上記のリンクが削除されている/機能していない場合に備えて)。

ここに画像の説明を入力してください

上の画像では、「$ scope.c」はどのデータバインディング(マークアップ)でも使用されていないため、監視されていません。他の2つ($scope.aおよび$scope.b)が監視されます。

ここに画像の説明を入力してください

上記の画像から:それぞれのブラウザーイベントに基づいて、AngularJSはイベントをキャプチャし、ダイジェストサイクルを実行し(変更のすべての監視を通過します)、監視機能を実行し、DOMを更新します。ブラウザイベントでない場合は、$applyまたはを使用して、ダイジェストサイクルを手動でトリガーできます$digest

詳細$apply$digest

ここに画像の説明を入力してください


17

ある$watchGroup$watchCollection同様。具体的に$watchGroupは、DOMオブジェクトではないビューに複数のプロパティを持つオブジェクトを更新する関数を呼び出す場合に非常に役立ちます。たとえば、キャンバス、WebGL、サーバーリクエストの別のビューなどです。

ここでは、ドキュメントのリンク


私はについてコメントしたでしょうが、$watchCollectionあなたがすでにそうしたのを私は見ます。 これは、AngularJSサイトからのドキュメントです。彼らは$watch深さの非常に素晴らしい視覚を提供します。情報がページの下部に近いことに注意してください。
JabberwockyDecompiler 2015

15

退屈で眠そうな上記すべてを読み終えてください(申し訳ありませんが、本当です)。非常に技術的で、詳細で、詳細で、ドライ。なぜ私は書いているのですか?AngularJSは巨大であるため、相互に関連する多くの概念が、誰でも気が狂う可能性があります。私はしばしば自分自身に尋ねました、私はそれらを理解するのに十分賢くないですか?番号!技術を説明することができる人がほとんどいないためですすべての用語です!わかりました、試してみましょう:

1)それらはすべてイベント駆動型のものです。(笑い声は聞こえますが、読み進めてください)

イベントドリブンが何であるかわからない場合は、ページにボタンを配置すると思います。「オンクリック」を使用して、ボタンを関数と接続し、ユーザーがクリックして、内部に埋め込んだアクションをトリガーするのを待ちます。関数。または、SQL Server / Oracleの「トリガー」を考えてみてください。

2)$ watchは「オンクリック」です。

特別なのは、2つの関数をパラメーターとして取ることです。1つはイベントからの値を与え、2つ目は値を考慮に入れます...

3)$ digestは精力的にチェックするボスです。

4)$ applyは、フェイルプルーフのように、手動で実行する方法を提供します(オンクリックで起動しない場合は、強制的に実行します)。

それでは、視覚化してみましょう。これを想像して、アイデアをつかみやすくします。

レストランの中、

-ウェイター

顧客からの注文を受けることになっていますが、これは

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

-すべてのウェイターが起きていることを確認するために走り回っているマネージャー。顧客からの変更の兆候に対応します。これは$digest()

-OWNERは、要求に応じて全員を駆り立てる究極の力を持っています。これは$apply()


2
これは5歳で理解できます。こういう答えに感謝します。+1
Chris22
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.