結論
完全に理解した(小並感)
Pipe、思ったよりすごいです。
@shuheiさんのコメントのお陰でめちゃくちゃ前進できました
準備
Pipeの作成
PipeはPipe
クラスを継承したクラスで定義します。Pipe
クラスを継承したクラスが備えていなければならないメソッドはsupports
、transform
の2つです。また、Pipe
のインスタンスを作成するためのPipeFactory
を作成する必要があります。PipeFactory
はcreate
メソッドを持つ必要があります。この2つは必ずしも別のクラスである必要はなく、Pipe自身がPipeFactoryとなることも可能です。
文字列を受け取り、2回繰り返すPipeとそのFactoryを次のように定義します
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を使うという優先順位付けの仕様のためです。
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の起動時のバインドで登録します。
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を作ります。
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() {}
}
<double-value [text]="text"></double-value>
import "package:angular2/angular2.dart";
@Component(selector: "double-value", properties: const {"text": "text"})
@View(templateUrl: "component/double.html")
class DoubleComponent {
String text;
}
<h2>Double Pipe</h2>
<span>{{ text | double }}</span>
便宜上専用のComponentを作ってますが、ルートのhtmlの中でももちろん使用可能です。
属性の値の中で使う
*for
の中で使うようなシチュエーションです。Directiveへ値が渡される前にPipeが適用されます。直前のapp.htmlを次のように書き換えると、Directiveへ値が渡されるときと内部とで2回Pipeが作用します。
<double-value [text]="text | double"></double-value>
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() {}
}
次の例のように、プロパティのバインドを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() {}
}
サポートしないオブジェクトが来た時の挙動
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メソッドがないとダメだっていうエラーは出すくせに)
くらいです。
以上。