Code

2014年7月15日火曜日

AngularJS Scope と $apply

 今日時間ができたので、Angular の動きをちょっと追ってみました。module の登録は一つの難しいところですし、injector という概念も難しいと思います。ただ、実際使うとき、Scope というのはかなり重要です。
 基本的にAngularJS は Compile 段階で、必要に応じて、Scope を作ります。さらに ng-scope というクラスを該当要素に追加されます。Scope を取得するには、angular.element(el).scope() で scope object が取得できます。ng-app の要素に対して、rootscope が作られます。
 Scope は一つの object ですので、その prototype 中に、$apply という関数はあります。これはどういうとき使うかというと、Angular の世界に何かイベントを引き起こす時使います。イベントを引き起こすというのは model を変更したり、メッセージを $emit, $broadcast したりする場合です。
 さらに遡ると、JavaScript の中に、イベントハンドラーは、jQuery の proxy などの bind 関数を使わない限り、基本的に window Scope で実行されるので、window Scope は Angular の Scope と違うため、単純にその中のでデータを修正したりすると、Angular の $digest 処理が実行されません。そうすると、$watch のデータのリフレッシュなどは Angular が検知できなくて、画面のリフレッシュもできません。
 一つの例として、もし directive の link 関数で、要素に何かイベントハンドラーを bind したら、さらにこのイベントが発生したことを、 Angular の世界にそれを通知する場合、該当要素の Scope を取得して、$apply 関数を呼び出す必要があります。
    if (ctrl.$viewValue !== value) {
      scope.$apply(function() {
        ctrl.$setViewValue(value);
    });
 
 実際のソースコードを見てみると、Angular は compile 段階で、HTML のテンプレートを保存したり、$$watcher に expression を追加したりしています。その後、 link 関数を返して、呼び出されます。その link 関数の中では、イベントハンドラーの追加や、jQuery UI の呼び出すことなどができます。さらに、何か変化がありましたら、Scope の $apply が呼び出されますので、 Angular の loop に入って、HTML のリフレッシュが実行されます。
 例えば、ng-model directive の実行順番は:
 Angular が bootstrap するとき、すべての directive を compile して、link 関数を呼び出します。このとき、ng-model の入っている要素の change イベントにイベントハンドラーをバンドルされます。キーボードのキーを押したとき、この change イベントが発生しますので、ハンドラー関数が呼び出されます。
 その後、 interpolation がこの directive を $watch リストに追加します。-> すべての directive とモジュールを初期化完了したら、Angular の loop から抜き出します。

 もしユーザーが何かのキーを input に押したら、ng-model のイベントハンドラーが呼ばれて、$apply 関数で model の value を変更します。
 $apply 関数の中に $digest 関数が呼ばれて、すべての登録した $watch 関数を一通り実行されます。
 これが終わったら、interpolation が実行されますので、HTML の変更などが実施されます。
 次、キーを押したイベントハンドラーの実行が終わりますので、Browser がリフレッシュされます。
 
 ちょっと複雑のように見えますが、実際どの directive もこのようなループが実行されます。一つ理解すれば、Angular はどのように動くかはたいてい明確になります。
 それでは。

0 件のコメント:

コメントを投稿