myHideを実装してみて、Angular2を分かった気になる

[Wantedly Advent Calendar 2015](http://qiita.com/advent-calendar/2015/wantedly) 17日目の投稿です

Wantedlyエンジニアの @KentoMoriwaki です。

# Angular2 触ってみませんか？

Angular2.0 betaが遂にリリースされましたが、後方互換性のなさや、TypeScriptで書かれていることもあり、なかなかとっつきづらい印象がありました。

しかし、実際に触って見ると、1.xで不満だった部分が解消され、かなり綺麗に記述できるようになっていたので、結構気に入ってしまいました。

そこで、Angular1でよくお世話になっている`ngHide`Directiveの簡易版`myHide`をAngular1と2で実装して比較してみることで、みなさんにもAngular2を分かった気になってもらえればと思い、この記事を書きました。

対象はAngular1をちょろっとでも触ったことがある人です。TypeScriptは知らない人でも分かるように書いたつもりです。

# Angular1のmyHide

まずは、Angular1の方で書いてみます。
`my-hide` Directiveは与えられたプロパティの値の変化に伴って、`ng-hide`classが付いたり消えたりするものです。
Directiveを使用するhtmlはこんな感じになります。

```html
<div my-hide="hidden"> 
  Hide me!
</div>
```

`hidden`の値により、`Hide me!`の表示・非表示が切り替わるイメージです。

Directiveの定義はこんな形になりますね。

```js
angular.module('myApp').directive('myHide', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attr) {
      scope.$watch(attr.ngHide, function(value) {
        element[value ? 'addClass' : 'removeClass']('ng-hide')
      });
    }
  };
});
```

本家の`ngHide`では、$animateを使っていたり、パフォーマンスのための設定などが書かれていますが、大体の実装は同じです。

やっていることを簡単に説明すると、`app.directive`にDirectiveの名前と、その設定を返す関数を渡しています。このDirectiveが使用される要素が見つかった時に、設定中のlink関数が実行され、`hidden`パラメータの変化を監視を開始し、変化に伴いDOMを操作して`ng-hide`クラスの付け替えを行うようになります。

# Angular2のmyHide

では、この`myHide`をAngular2で書いてみましょう。
まず、htmlの方ですが、明らかに1とは変わっています。

```html
<div [myHide]="hidden"> 
  Hide me!
</div>
```

ハイフン繋ぎで書かれていた`my-hide`が、camelCaseの`myHide`に代わり、謎の`[]`で囲まれています。

`ng-hide`から`myHide`になったのは、Directiveの定義との統一性が取れたので嬉しいです。よく定義の方で`app.directive("my-hide")`とか書き間違ってしまうことがあったので。

## データバインディング

`[]`が何を表しているのか直感的に分かる人はいないでしょう。
`[]`の仲間に`()`と`[()]`も存在しますが、これらは**バインディングの種類を明示的にする**ためのものです。
Angular1では、ビューだけみても、どんなDirectiveが定義されているか知らなければ、どの属性がバインディングされているのか分かりにくかったですが、Angular2では**ひと目見ただけでどのようにバインディングされているのかが分かるようになりました**。
Angular2には以下のような3種類のバインディングに分けられています。

- `[]` data => view の一方向バインディング
    -  例: `[myHide]="hidden"` 
    -  データの変更によりビューを変更する際に使用する。
    -  **Property Binding**と呼ばれる。
- `()` view => data の一方向バインディング
    - 例: `(click)="onClick()"`
    - ビューのイベントによりデータを変更する際に使用する。
    - **Event Binding**と呼ばれる。
- `[()]` 双方向バインディング
    - 例: `[(ngModel)]="model.name"`
    - `[]`と`()`を組み合わせて双方向のバインディングを実現するためのシンタックス。
    - 実際、この例は`[ngModel]="model.name" (ngModelChange)="model.name=$event"`のように２つに分けて書くこともできる。

このシンタックスは初めはとても奇妙に見えますが、一回理解してしまえば、なんでも`ng-foo="bar"`と書いていたのと比較して、構造の把握が簡単になったと思います。しかし、HTMLのテンプレートエンジンとの親和性が更に下がったのではないかと懸念もあります。


次にDirectiveの定義をしていきましょう。
TypeScriptで書いていますが、初めての人も分かるように説明したいと思います。

```ts
import {Directive, Input, ElementRef, Renderer} from 'angular2/core';

@Directive({
  selector: '[myHide]'
})
export class HideDirective {
  @Input('myHide') set value(val: boolean) {
    this.renderer.setElementClass(this.el, 'ng-hide', val);
  }

  constructor(private el: ElementRef, private renderer: Renderer) { }
}
```

やっていることは、HideDirectiveクラスを定義しているだけです。
`@XXX`というのは見慣れない表記があるので混乱してしまいますが、一旦無視して読んで見れば、大体把握できると思います。

## Dependency Injection

まずは、HideDirectiveクラスのコンストラクタを見てみましょう。

```ts
constructor(private el: ElementRef, private renderer: Renderer) { }
```

２つの引数、`ElementRef`型の`el`と、`Renderer`型の`renderer`を受けて、それをインスタンス変数に設定しています。`ElementRef`と`Renderer`は、Angular2で定義されているクラスで、DIにより初期化時にそれぞれのクラスのインスタンスが渡されます。

次に、`ng-hide`クラスの付け替えを行っている部分を見てます。

```ts
@Input('myHide') set value(val: boolean) {
  this.renderer.setElementClass(this.el, 'ng-hide', val);
}
```

`@Input`を無視してみると、`set value(val: boolean) {}` でvalueプロパティのセッターを定義しています。
セッター内で、受けた値に応じて、要素に`ng-hide`クラスを付けたり外したりしています。

Angular2ではサーバーサイドやweb workerでのレンダリングを可能にするために、DOMを直接操作することは推奨されていません。そのため、先ほどコンストラクタで受け取った `ElementRef`と`Renderer`を用いて間接的に操作する必要があります。

## Decoratorによる

残りの`@Input`の部分は、Angular1の`scope.$watch`に対応する部分です。

この部分を理解するためには、`@XXX`のような書き方を説明しておく必要があります。これは、**TypeScriptのDecorator**という仕組みです。
クラスやメソッド、プロパティの定義の直前に書くことで、その対象にメタ情報を付与したり、拡張したりすることができる機能で、Angular2ではこの書き方を多用しており、一瞬たじろいでしまいますが、**メタ情報の定義だと分かってしまえば楽勝**です。

`@Input('myHide')`は、`value`プロパティに**Property Binding**のためのメタ情報を付与しています。これで、`myHide`のプロパティ名が変化したときに、このセッターが呼ばれて、要素に`ng-hide`クラスが付けられるという流れが実現します。

最後に、`@Directive`Decoratorでディレクティブの設定を書いて完了です。

```ts
@Directive({
  selector: '[myHide]'
})
```

このDirectiveは属性として使用するので、selectorに`[myHide]`とcssセレクタを書きます。
Angular1の`restrict: 'AEC'`という呪文から開放されて、直感的になり嬉しい限りですね。

ここまで書けば、Angular2の大体の雰囲気が分かったんじゃないかと思います。

Angular1では、簡単なDirectiveを定義するためにも、書き方の雛形を覚えて、`link`や`scope`, `$watch`などを使いこなす必要がありましたが、Angular2では、基本的にはクラス定義だけで、Decoratorで幾つかの付加情報を付けるだけで良くなり、かなり綺麗なコードが書けるようになったと感じます。
そのおかげでテストを書くのも簡単になっているんじゃないかと期待していますが、その辺りはまた今度触ってみたいと思います！

## まとめ

- 基本的にクラスを定義するだけ
- Decorator便利
- Bindingがひと目みて分かるようになった
- DOMは触らない
- Angular2怖くない