Angular2はいかにしてオブジェクトの変更を監視しているのか

こんにちは、laco0416です。

今回はAngular2がいかにしてオブジェクトの変更を監視し、データバインディングを解決しているのかを解き明かします。

## 結論
[この部分](https://github.com/angular/angular/blob/7ae23adaff2990cf6022af9792c449730d451d1d/modules/angular2/src/core/application_ref.ts#L374)でループとtick処理を実装していた。

```ts
ObservableWrapper.subscribe(this._zone.onTurnDone,
                                  (_) => { this._zone.run(() => { this.tick(); }); });
```

## 調査開始
Angular2は`$apply`がないのにどうやってオブジェクトの変更をビューに反映しているんだろう？という疑問から調査を開始。

そもそも、Componentのプロパティに変更を加えたときに何かイベントが発生しているわけではない（object.ObserveもProxiesも使っていない)ので、何かしらのタイミングで別のメソッドから変更があるかどうかをチェックしているはず。

ということで変更を検知する処理を探索、AbstractChangeDetectorに`detectChanges`メソッドを発見。

https://github.com/angular/angular/blob/fcc7ce225ec6b6abc8935c2a024941ee53dce1e6/modules/angular2/src/core/change_detection/abstract_change_detector.ts#L76

```ts
  detectChanges(): void { this.runDetectChanges(false); }
```

このメソッドが呼ばれると、ChangeDetectorが保存している状態と現在の状態を比較して、変更点をリストアップするらしい。

次にこの`detectChanges`が呼ばれている部分を探す。発見。

https://github.com/angular/angular/blob/7ae23adaff2990cf6022af9792c449730d451d1d/modules/angular2/src/core/application_ref.ts#L471

```ts
  tick(): void {
    if (this._runningTick) {
      throw new BaseException("ApplicationRef.tick is called recursively");
    }

    var s = ApplicationRef_._tickScope();
    try {
      this._runningTick = true;
      this._changeDetectorRefs.forEach((detector) => detector.detectChanges());
      if (this._enforceNoNewChanges) {
        this._changeDetectorRefs.forEach((detector) => detector.checkNoChanges());
      }
    } finally {
      this._runningTick = false;
      wtfLeave(s);
    }
  }
```

`ApplicationRef_`クラスのtick()メソッドの中で呼ばれていた。ざっと上から処理を追うと、

1. tickが入れ子になっていないかのチェック（1ApplicationRefにつき同時に走るtickは1つ）
2. `_tickScope`の呼び出し。中はプロファイリング用の処理だった。無視してOK
3. tick処理を開始。フラグを立てる
4. ApplicationRefが持っているChangeDetectorすべてに`detectChanges`を実行
5. `_enforceNoNewChanges`がtrueならすべてのChangeDetectorを変更がなかったものとする（`ngAfter**`系のライフサイクルが発生しないっぽい）
6. tick処理を終了。フラグを下ろす
7. プロファイリングを終了する。無視してOK

アプリケーション全体のデータバインディングを解決するメソッドが分かった。これがAngularJSの$digestループ相当のものらしい。あとはこれが呼ばれている場所がわかればいい。

というわけでtick()を呼び出している部分を探索、発見。

https://github.com/angular/angular/blob/7ae23adaff2990cf6022af9792c449730d451d1d/modules/angular2/src/core/application_ref.ts#L374

```ts
constructor(private _platform: PlatformRef_, private _zone: NgZone, private _injector: Injector) {
    super();
    if (isPresent(this._zone)) {
      ObservableWrapper.subscribe(this._zone.onTurnDone,
                                  (_) => { this._zone.run(() => { this.tick(); }); });
    }
    this._enforceNoNewChanges = assertionsEnabled();
  }
```

`ApplicationRef_`のコンストラクタである。bootstrap関数によってアプリケーションの開始時に一度だけ呼ばれる部分。当たり前といえば当たり前である。

とはいえ初見ではこれがtickループの実装だとはわからないと思うので、ひとつずつ解説する。

### `ObservableWrapper.subscribe` 
ObservableWrapperの実装はこれ
[class ObservableWrapper](https://github.com/angular/angular/blob/fcc7ce225ec6b6abc8935c2a024941ee53dce1e6/modules/angular2/src/facade/async.ts#L26)

RxJSのObservableをラップし、EventEmitterと協調するためのAngular2用の非同期処理用便利クラスである。Observableの処理をWrapperのstaticメソッドで行うことができるのでRxJSを隠蔽できる。

`subscribe`メソッドは、第1引数に渡されたEventEmitterのイベントが発行されるたびに第2引数の関数が実行される。

### `this._zone.onTurnDown`
subscribeの第1引数に渡されたこれは前述のとおりEventEmitterである。つまり、このイベントが発火されるたびに第2引数の処理が走る。

`this._zone`の型は`NgZone`だが、これはZone.jsのZoneを拡張したAngular2用のZoneである。

[class NgZone](https://github.com/angular/angular/blob/fcc7ce225ec6b6abc8935c2a024941ee53dce1e6/modules/angular2/src/core/zone/ng_zone.ts#L92)

どのように拡張しているかというと、
Zoneの`run`が実行されるたびに自身の`onTurnStart`を発火し、処理が終了すると`onTurnDone`を発火するようになっている。

[このソース](https://github.com/angular/angular/blob/fcc7ce225ec6b6abc8935c2a024941ee53dce1e6/modules/angular2/src/core/zone/ng_zone.ts#L352)にある`_notifyOnTurnStart`と`_notifyOnTurnDone`がそれである。


### `this._zone.run(() => { this.tick(); }`
これはApplicationRefが持っているZone中でtick処理を行っているだけである。Zoneについては本稿では扱わないが、複数の非同期処理をグループ化し、コンテキストを共有したもののように思ってもらえればよい。同じZone内で起きたエラーを一括でハンドルしたり、非同期のスタックトレースを取得できたりする。

[angular/zone.js: Implements Zones for JavaScript](https://github.com/angular/zone.js/)


これですべての謎が解けた。まとめると以下のようになる。

1. ApplicationRefが作成される（bootstrap関数の中で作られる）
2. ApplicationのNgZoneが作成され、tickループが作られる
3. 各Componentが自身のChangeDetectorをApplicationに登録する（これはコンポーネントツリー構築時にされている）
4. tickが呼ばれる
5. すべてのChangeDetectorが変更チェックし、データバインディングを解決する
6. tick処理が終わると`onTurnDone`イベントが発火する
7. `onTurnDone`イベントを受けてtickを実行する
8. 4に戻る

イベントドリブンな再帰ループ？とでも言うのだろうか。ともかくこういう仕組みで動いている。setIntervalとかではない。

## 所感
RxJSとZone.jsとの合わせ技だが、わかってしまえばシンプルだった。ちなみに処理の追跡は全部GitHub上で出来たので楽だった。

Zone.jsについてはまた後日記事を書こうと思う。