Dart
angular
Angular2

Angular2 Pipeメモ

More than 3 years have passed since last update.


結論


今は無理

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

Pipe、思ったよりすごいです。

@shuheiさんのコメントのお陰でめちゃくちゃ前進できました


準備


Pipeの作成

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

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


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追加

keyValDiffiterableDiffasync等の組み込みの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の起動時のバインドで登録します。


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を作ります。


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() {}
}



app.html

<double-value [text]="text"></double-value>



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;
}


<h2>Double Pipe</h2>

<span>{{ text | double }}</span>

スクリーンショット 2015-05-04 21.43.22.png

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


属性の値の中で使う

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


app.hmtl

<double-value [text]="text | double"></double-value>


スクリーンショット 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

次の例のように、プロパティのバインドを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


サポートしないオブジェクトが来た時の挙動

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メソッドがないとダメだっていうエラーは出すくせに)

くらいです。

以上。