Angular2 Pipeメモ


# 結論
<del>
今は無理
</del>

**完全に理解した(小並感)**

Pipe、思ったよりすごいです。
@shuheiさんのコメントのお陰でめちゃくちゃ前進できました

# 準備

## Pipeの作成

Pipeは`Pipe`クラスを継承したクラスで定義します。`Pipe`クラスを継承したクラスが備えていなければならないメソッドは`supports`、`transform`の2つです。また、`Pipe`のインスタンスを作成するための`PipeFactory`を作成する必要があります。`PipeFactory`は`create`メソッドを持つ必要があります。この2つは必ずしも別のクラスである必要はなく、Pipe自身がPipeFactoryとなることも可能です。

文字列を受け取り、2回繰り返すPipeとそのFactoryを次のように定義します

```dart:pipe/double.dart
import 'package:angular2/change_detection.dart';

class DoublePipeFactory extends Pipe {
  bool supports(obj) {
    return obj is String;
  }

  dynamic transform(dynamic value) {
    return "${value}${value}";
  }

  Pipe create(ChangeDetectorRef cdRef) {
    return this;
  }
}
```

`Pipe`クラスからオーバーライドするのは次の2つのメソッドです。

### `bool supports(obj)`
dynamic型(JSだとany?)のオブジェクトが与えられ、パイプが有効かどうかを判定します。与えられるオブジェクトはPipe使用時の左辺です。今回は文字列に限定するためにString型のときにtrueを返しています

### `dynamic transform(dynamic value)`
Pipeの変換処理を行います。戻り値の型が引数と一致している必要はなく、例えばリストを受け取って先頭だけを返すPipeなども作成できます。

`PipeFactory`として振る舞うために次のメソッドを実装しています。

### `Pipe create(ChangeDetectorRef cdRef)`
`Pipe`のインスタンスを返します。今回はPipe自身がFactoryを兼ねているので`this`を返します。引数の`cdRef`は更新タイミングの制御に用いるものですが、同期処理の範囲であれば使う必要はないです。具体的な使い方は組み込みの`AsyncPipe`の実装を見ると良いと思います。

## PipeRegistryへの登録
作成したPipeは`PipeRegistry`へ登録することでAngularがPipeとして使用できるようになります。この登録部分にまだ簡潔なAPIが存在していないので割と力押しな実装になっています。今後改善の余地あり？

### defaultPipeRegistryへのPipe追加
`keyValDiff`や`iterableDiff`、`async`等の組み込みのPipeが登録されている`defaultPipeRegistry`から内部のマップである`config`を抜き出して、自作のPipeをねじ込んだ新しいコンフィグを作成します。マップに渡すのはPipeでもPipeFactoryでもなく、 **PipeFactoryのリスト** である点に注意です。これは同一ネームのPipeFactoryのリストを頭からPipeインスタンスを取り出して`supports`を呼び出し、最初に`true`が帰ってきたPipeを使うという優先順位付けの仕様のためです。

```dart
import 'package:angular2/change_detection.dart';
import 'double/double.dart';
import 'filter/filter.dart';

dynamic get pipes {
  var config = defaultPipeRegistry.config;
  config["double"] = [new DoublePipeFactory()];
  return config;
}
```

### アプリケーションへの登録
作成したコンフィグをAngular2の起動時のバインドで登録します。

```dart:main.dart
import "package:angular2/src/reflection/reflection.dart";
import "package:angular2/src/reflection/reflection_capabilities.dart";
import 'package:helloworld/component/app_component.dart';
import 'package:angular2/di.dart';
import 'package:helloworld/pipe/pipes.dart';

main() {
  reflector.reflectionCapabilities = new ReflectionCapabilities(); //dart版では暫定的に必要らしいコードなので無視

  var injectables = [bind(PipeRegistry).toValue(new PipeRegistry(pipes))];

  bootstrap(AppComponent, injectables);
}
```

# 使う
登録までできたらあとは使うだけです。

## `{{}}`の中で使う
一番シンプルな形です。`text`属性に与えた文字列を2回繰り返して表示する`double-value`というComponentを作ります。

```dart:app_component.dart
import "package:angular2/angular2.dart";
import 'package:helloworld/component/double/double_component.dart';

@Component(selector: 'my-app')
@View(
    templateUrl: 'component/app.html',
    directives: const [DoubleComponent])
class AppComponent {
  var text = "ABC";

  AppComponent() {}
}
```

```html:app.html
<double-value [text]="text"></double-value>
```

```dart:double_component.dart
import "package:angular2/angular2.dart";

@Component(selector: "double-value", properties: const {"text": "text"})
@View(templateUrl: "component/double.html")
class DoubleComponent {
  String text;
}
```

```html
<h2>Double Pipe</h2>
<span>{{ text | double }}</span>
```

![スクリーンショット 2015-05-04 21.43.22.png](https://qiita-image-store.s3.amazonaws.com/0/20253/bcd30d36-fd3b-0dc2-79fb-66f4c6f4b2b7.png "スクリーンショット 2015-05-04 21.43.22.png")



便宜上専用のComponentを作ってますが、ルートのhtmlの中でももちろん使用可能です。

## 属性の値の中で使う
`*for`の中で使うようなシチュエーションです。Directiveへ値が渡される前にPipeが適用されます。直前のapp.htmlを次のように書き換えると、Directiveへ値が渡されるときと内部とで2回Pipeが作用します。

```html:app.hmtl
<double-value [text]="text | double"></double-value>
```

![スクリーンショット 2015-05-04 21.46.49.png](https://qiita-image-store.s3.amazonaws.com/0/20253/f8c25e0d-2b35-d76f-c0ce-f4a4aa7e930a.png "スクリーンショット 2015-05-04 21.46.49.png")

## Directive定義中で使う
この用法のせいで相当混乱しました。html側ではなくスクリプト側でPipeを適用することができる記法です。`@Component`中の`properties`の中で使用します。

Directiveに渡された値が内部のプロパティにバインドされた段階でPipeが適用されるので、double_component.dartを次のように書き換えることで、さらに一回追加でPipeを通され、合計3回の繰り返しが発生します。

```
import "package:angular2/angular2.dart";

@Component(selector: "double-value", properties: const {"text": "text | double"})
@View(templateUrl: "component/double.html")
class DoubleComponent {
  String text;
  DoubleComponent() : super() {}
}
```

![スクリーンショット 2015-05-04 21.49.54.png](https://qiita-image-store.s3.amazonaws.com/0/20253/7ebeee55-6a80-9278-d6b8-3bdf22bd8699.png "スクリーンショット 2015-05-04 21.49.54.png")

次の例のように、プロパティのバインドをsetterにすることでPipeがバインド後にPipeが作用していることがわかります

```
import "package:angular2/angular2.dart";

@Component(
    selector: "double-value", properties: const {"textChange": "text | double"})
@View(templateUrl: "component/double.html")
class DoubleComponent {
  String text;

  set textChange(String newText) {
    this.text = newText + "_";
  }

  DoubleComponent() : super() {}
}
```

![スクリーンショット 2015-05-04 21.56.22.png](https://qiita-image-store.s3.amazonaws.com/0/20253/fab9adbd-aac0-fece-ab2f-fe09935c71f4.png "スクリーンショット 2015-05-04 21.56.22.png")

# サポートしないオブジェクトが来た時の挙動
`double`のPipeに例えば`100`が渡された場合はPipeRegistryの`double`に登録されたPipeFactoryの中から`supports(100) == true`となるPipeを探し、見つからなかった場合はエラーが出てきます。

```
Cannot find 'double' pipe supporting object '100' in [text| double in AppComponent]
```


# まとめ
今は無理とか適当な事言ってすみませんでした！！
若干早い感はまだ否めませんが、実用可能なレベルにはなっていました。
Dart版なので細かいところがJSと違うかもしれませんがそこは頑張ってください。DartとJSの違いなんてAngular2の闇の前では些事です。

今後改善して欲しい点としては

* PipeRegistry周りもうちょっとAPI整備して綺麗に書けるようにしてくれ
* なぜPipeFactoryはクラスがないのか…(createメソッドがないとダメだっていうエラーは出すくせに)

くらいです。

以上。