Wantedly Advent Calendar 2015 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はこんな感じになります。
<div my-hide="hidden">
Hide me!
</div>
hidden
の値により、Hide me!
の表示・非表示が切り替わるイメージです。
Directiveの定義はこんな形になりますね。
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とは変わっています。
<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"
のように2つに分けて書くこともできる。
- 例:
このシンタックスは初めはとても奇妙に見えますが、一回理解してしまえば、なんでもng-foo="bar"
と書いていたのと比較して、構造の把握が簡単になったと思います。しかし、HTMLのテンプレートエンジンとの親和性が更に下がったのではないかと懸念もあります。
次にDirectiveの定義をしていきましょう。
TypeScriptで書いていますが、初めての人も分かるように説明したいと思います。
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クラスのコンストラクタを見てみましょう。
constructor(private el: ElementRef, private renderer: Renderer) { }
2つの引数、ElementRef
型のel
と、Renderer
型のrenderer
を受けて、それをインスタンス変数に設定しています。ElementRef
とRenderer
は、Angular2で定義されているクラスで、DIにより初期化時にそれぞれのクラスのインスタンスが渡されます。
次に、ng-hide
クラスの付け替えを行っている部分を見てます。
@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でディレクティブの設定を書いて完了です。
@Directive({
selector: '[myHide]'
})
このDirectiveは属性として使用するので、selectorに[myHide]
とcssセレクタを書きます。
Angular1のrestrict: 'AEC'
という呪文から開放されて、直感的になり嬉しい限りですね。
ここまで書けば、Angular2の大体の雰囲気が分かったんじゃないかと思います。
Angular1では、簡単なDirectiveを定義するためにも、書き方の雛形を覚えて、link
やscope
, $watch
などを使いこなす必要がありましたが、Angular2では、基本的にはクラス定義だけで、Decoratorで幾つかの付加情報を付けるだけで良くなり、かなり綺麗なコードが書けるようになったと感じます。
そのおかげでテストを書くのも簡単になっているんじゃないかと期待していますが、その辺りはまた今度触ってみたいと思います!
まとめ
- 基本的にクラスを定義するだけ
- Decorator便利
- Bindingがひと目みて分かるようになった
- DOMは触らない
- Angular2怖くない