Angular2 Dartを改めて使ってみたのでノウハウを忘れないうちにメモ

ハッピーバースデー俺。

さて、Dart界隈で有望そうなクライアントサイドフレームワークといえばフルスタック双方向バインディングでおなじみなAngular2さんくらいのものなのですが、少なくとも1年前はeveryday breaking changes! なﾋｬｯﾊｰ状態で使い物にならない雰囲気を感じたものです。

が、あれから1年の間に[いろんな事](http://victorsavkin.com/post/110170125256/change-detection-in-angular-2)がおこり、GoogleマジAngular2をDartで使ってるからね！見捨ててないからね！[アピール](http://angularjs.blogspot.jp/2015/11/how-google-uses-angular-2-with-dart.html)をしてきたのであらためて使ってみたいと思います。

## とりあえず適当なのを作ってみた

![Angular2_MaterialDesign.png](https://qiita-image-store.s3.amazonaws.com/0/3865/bbb0925c-816d-33cc-aab4-860a4fbe99dd.png "Angular2_MaterialDesign.png")

**デモページ**
http://takyam-git.github.io/angular2_test_20151212/

**ソースコード**
https://github.com/takyam-git/angular2_test_20151212


アプリケーションの概要としては、右側のフォームで入力した文字列が、左側に順番に表示されるだけの糞みたいなアプリケーションですね。

愚にもつかないアプリケーションではあるのですが、私はAngularに全然明るくないので許してちょんまげ。（半日かけてここまで出来ただけでも驚きだよ！）

で、今回これを作ってみて得られた知見を披露したうえで、後世のAngular2のDartバージョンでアプリケーションを作れる時代にきっと同じポイントでハマる人が出てくると思うのでその一助になればと思っております。
感想は最後に書いてます。

とはいえ

> Angular 2 is currently in **Developer Preview**. We recommend using Angular 1.X for production applications:

ってREADMEの先頭に書いてある程度にはα版なので、この知見もすぐ使えなくなると思うけどね！

## 知見を箇条書き

* コンポーネントが依存してるコンポーネントは `derectives` に渡してあげる必要がある
* `NgIf` や `NgFor` といったよく使うディレクティブも `derectives` に渡してあげる
* コンポーネントからコンポーネントにオブジェクトを渡すときは `@Input`
* `nl2br` 的モジュールは `Pipe` を使うと良さそう
* `Pipe` を定義するときには `@Injectable` を忘れずに
* テンプレートでHTMLエスケープせずに埋め込みたい場合は `[innerHtml]`
* `event.preventDefault()` みたいなのしたいときはテンプレートから `$event` を渡す

## アプリケーションの構成

![Kobito.w7nb37.png](https://qiita-image-store.s3.amazonaws.com/0/3865/3e5c0574-a96f-cb91-fedf-e9cc69bf02f6.png "Kobito.w7nb37.png")

今回作ったclassは↑の図のような感じ。大した量じゃないね。
1個の(シングルトンな)Threadに複数のRes（書き込み）が紐づく感じ。

![Angular2_MaterialDesign.png](https://qiita-image-store.s3.amazonaws.com/0/3865/36d90701-2c63-2641-596c-789d87fbbd5f.png "Angular2_MaterialDesign.png")

各Componentは画面上でいうとそれぞれ↑みたいな感じ。

AppComponentの子にThreadComponentとPostComponentの2つがいる。
ThreadComponentの子に書き込みの数だけResComponentがいる。

### AppComponent

階層の上から順に見たほうが構成が把握しやすいと思うのでまずはAppComponentを見てみましょ。

```dart
@Component(
    selector: 'my-app',
    templateUrl: 'app_component.html',
    directives: const [ThreadComponent, PostComponent]
)
class AppComponent {
  Thread thread = new Thread();
}
```

```html
<nav class="navbar navbar-inverse navbar-fixed-top">
	<div class="container">
		<div class="navbar-header">
			<a class="navbar-brand" href="#">Simple BBS (現在の書き込み数: {{thread.resList.length}}個)</a>
		</div>
	</div>
</nav>
<div class="container">
	<div class="row">
		<div class="col-md-8">
			<my-app-thread></my-app-thread>
		</div>
		<div class="col-md-4">
			<my-app-post></my-app-post>
		</div>
	</div>
</div>
```

#### 依存関係を明示的に宣言してあげる

Dartのコードの方はThreadのシングルトンを取得している他、`ThreadComponent` と `PostComponent` への依存を明示しているくらいです。

HTMLの方もBootstrapのいわゆるテンプレートそのままで、タイトルのところに書き込みの数を表示するために `{{thread.resList.length}}` があるのと、`ThreadComponent` と `PostComponent` を呼び出す `<my-app-thread></my-app-thread>` と `<my-app-post></my-app-post>` が書いてあるくらいであとは普通のHTML。

directivesがかなりイケてるなぁと思ったのは、ひとつのコンポーネントが依存しているコンポーネントを明示すれば十分で、たとえばこの後説明する `ThreadComponent` は `ResComponent` に依存してるわけなんですが、`AppComponent` はその事を知らなくていいし宣言しなくてよい。
あくまで自分自身が利用しているコンポーネントなりディレクティブを宣言してあげればよいので、非常に明快で分かりやすいですね。

あとは `thread.resList.length` なんですが、これはもうDartのオブジェクトのプロパティをHTML上で直接参照できていて、使い勝手として違和感が無いのはすごいなと思った。

### ThreadComponent

```dart
@Component(
    selector: 'my-app-thread',
    templateUrl: 'thread.html',
    directives: const [NgFor, NgIf, ResComponent]
)
class ThreadComponent {
  Thread _thread = new Thread();
  List<Res> resList;

  ThreadComponent() {
    this.resList = this._thread.resList;
  }
}
```

```html
<my-app-res *ngFor="#res of resList.reversed" [res]="res"></my-app-res>
<p *ngIf="resList.length == 0">右の投稿フォームから入力してください</p>
```

#### `NgIf`や`NgFor`もディレクティブとして使うための宣言が必要

こちらも同じように依存している `ResComponent` を `directives` に追加してあげてます。
`AppComponent`では使ってなかったので書いてませんでしたが、`ThreadComponent`では`*ngFor`と`*ngIf`をHTML上で利用しているので`NgFor`と`NgIf`も`directives`に追加しています。

#### DartのオブジェクトはDartのオブジェクトとして利用可能

あとは`Res`オブジェクトの配列である `Thraed.resList` を自身のプロパティ`resList`にセットする事でHTMLから`Res`のリストにアクセスできるようにしています。

`<my-app-res [res]="res"></my-app-res>` ですが、 `res` は `Res` のインスタンスで、それをそのまま渡せてる点がマジで便利です。すごい。ただしこれも渡せるようにするのに `ResComponent` 側で設定がいるので注意です（後述）。

`#res of resList.reserved` を見ると分かるように `.reserved` はDartのListのメソッドで、順序を反転したIterableを返してくれるのですが、そういったものも特にAngularだからどうのこうのを意識せずにHTML上で呼び出すことができます。すごい。

### ResComponent

```dart
@Component(
    selector: 'my-app-res',
    templateUrl: 'res.html',
    directives: const [NgIf],
    pipes: const [Nl2BrPipe]
)
class ResComponent {
  @Input() Res res;

  ResComponent();

  void destroy(Event event) {
    event.preventDefault();
    new Thread().removeRes(this.res);
  }
}
```

```html
<div class="panel panel-default">
	<div class="panel-heading">
		ID: {{res.id}} 投稿日時: {{res.postedAt.toString()}} <a (click)="destroy($event)" class="btn btn-default btn-sm">削除する</a>
	</div>
	<div class="panel-body" [innerHtml]="res.body|nl2br">
	</div>
</div>
```

これは結構ノウハウが詰まったComponentなんで細かく説明します。

#### directivesだけじゃなくpipesも宣言が必要

まず、directivesにHTMLで利用している`NgIf`を追加しています。
次に`ほげほげ\nふがふが`を`ほげほげ<br>ふがふが`にするための`Nl2BrPipe`を利用することを`pipes`で宣言しています。ディレクティブと同じように、利用するPipeも個別のコンポーネント毎に宣言が必要なようです。

#### `@Input()`アノテーションが無いと値を渡せない

つぎに `@Input() Res res;` についている `@Input()` アノテーションが重要で、
これがあることで `ThreadComponent` から `<my-app-res [res]="res">` のように、まるでComponentの引数のようにプロパティをセットする事ができるようになります。このアノテーションを設定していないと、利用する側が `[res]="res"`のように記述したとしてもComponentに渡ることはありません。

#### HTMLをテンプレートに突っ込みたい場合は`[innerHtml]`

`Nl2BrPipe`は[実装](https://github.com/takyam-git/angular2_test_20151212/blob/master/lib/src/pipes/nl2br.dart)を見ると分かるんですが、HTMLエスケープ+改行をBRタグに変換した **HTML文字列** を返してくれるのですが、それをAngularのテンプレート（HTML）上に直接埋め込もうとすると(例: `<div>{{res.body | nl2br}}</div>`)、HTML文字列をさらにHTMLエスケープしてしまいます。こういった場合は以前は `ng-bind-unsafe-html` のような機能を利用したりして埋め込んでいたようなのですが、現在は `[innerHtml]="res.body|nl2br"` のように、DOMのinnerHtmlに突っ込むのが割と正しいアプローチのようでした。

ぐぐると `[inner-html]` とかいろいろでてきますが `[innerHtml]` が正しいです。

#### イベントは`$event`変数を渡してあげればオッケー

最後に `(click)="destroy($event)"` とそれから呼び出される `destroy(Event event)` を見てもらえると分かるように、クリックイベントをキャンセルさせたい場合は `$event` を渡してあげることで、`event.preventDefault()`が呼び出せるようになるようです。詳細は分かっていないのですが、この `$event` 変数は特に明示的に宣言せずに使える変数になっているようです。

#### データを消せばDOMも消える

なお、 `new Thread().removeRes(this.res)` を実行することで、`Thread.resList` からこの`ResComponent`が持ってる`Res`が削除され、当然なが同じListを参照している `ThreadComponent.resList` からも消えますので、この `ResComponent` 自体も削除される、といった処理の流れになり、この削除ボタンが動作します。

明示的にDOMの削除のような事は一切せずに、データストアである `Thread` からデータを1件削除するとそのデータを表示していたDOMがちゃんと消えるあたり、ナウい感じはします。

### PostComponent

```dart
@Component(
    selector: 'my-app-post',
    templateUrl: 'post.html'
)
class PostComponent {
  Thread _thread = new Thread();

  PostComponent();

  void postNewRes(Event event, TextAreaElement resInput) {
    event.preventDefault();
    String resBody = resInput.value;
    resInput.value = '';
    this._thread.addRes(resBody);
  }
}

```

```html
<form (submit)="postNewRes($event,resInput)">
	<div class="form-group">
		<label for="resInput">投稿内容</label>
		<textarea #resInput id="resInput" class="form-control" rows="5"></textarea>
	</div>
	<button class="btn btn-block btn-primary">書き込む</button>
</form>
```

投稿フォーム部分で使ったノウハウとしては`<textarea #resInput>` のように `#resInput` でDOMを変数化（のようなイメージ）でき、`postNewRes($event, resInput)` のように関数呼び出し時にそのDOMのオブジェクトを渡すことができるようになります。

ここは本来は入力中の文字列をデータバインディングする設計のほうが美しいのですが、何かうまいこといかなかったのでDOMを直接Component側が操作するような対応になっています。

ComponentがDOMを操作する事は許されることでしょうし、必要不可欠な場面もあるとは思うのですが、やらなくてすむならやりたくないですね。

### Nl2BrPipe

```dart
@Pipe(name: "nl2br")
@Injectable()
class Nl2BrPipe implements PipeTransform {
  final _regexp = new RegExp('(\r\n|\n\r|\n|\r)');

  dynamic transform(dynamic value, List<dynamic> args) {
    return HTML_ESCAPE.convert(value).replaceAll(this._regexp, '<br>');
  }
}
```

以前は `class HogePipe extends Pipe {}` のような宣言方法だったようですが、現在はこのように `@Pipe()` アノテーションをつけたうえで `PipeTransform` をimplementsするのが正しいようです。また、Componentの`pipes`に渡せるようにするために`@Injectable()`アノテーションも併せて宣言しておく必要があるようでした。

## 感想

1年前と比較して、それほど開発しやすくなった印象はありませんでした。
[CHANGELOG](https://github.com/angular/angular/blob/master/CHANGELOG.md)を見てもBREAKING CHANGESがかなり並んでますし。

#### 開発しづらい理由

特にとっかかりとして開発しづらい理由は明確で、 **最新のDartの資料が無い** ことにつきます。
ドキュメントとしては[TypeScript](https://angular.io/docs/ts/latest/)が一番充実しているので、TypeScriptのドキュメントを参考にしながら紐解いていく感じになります。
とはいえそもそも公式ドキュメントの言葉足らず感がﾊﾟﾈｪので、結局はググることになるんですが、その際に出てくるものの大半が 「 **既にBreakingChangeされた情報** 」「 **Angular1.xの情報** 」「 **JSあるいはTypeScriptにおける情報** 」の3種類で構成されており、今まさに必要な 「 **Angular2.xのDartの場合の情報** 」を拾ってくる難易度が超高いです。

#### Angular2の出来の良さ

ある程度分かってみれば、非常に洗練された設計になっているし、セーフティだし、パフォーマンスも想像以上に良いしで、かなり良いフレームワークに仕上がってきている印象は受けるのですが、いかんせん「 **こうしたい場合にどうしたらいいの？** 」を探しだすコストがﾊﾟﾈｪので、早くBREAKINGなCHANGESが無くなってくれて、Angular2の確かな情報が出回る世界になるといいなぁと思います。

#### いまとこれからザックリまとめ

* 悪くない（他のDartのクライアントサイドフレームワークと比べると非常に良い出来）
* あるていど走りだしが辛いのは覚悟が必要
* 頑張って手に入れたノウハウは枯れる前に使えなくなる覚悟も必要
* 普通に考えてDeveloper Previewじゃなくなるまで待てばいいんじゃないかな