18
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Angular2 Pipeメモ

Last updated at Posted at 2015-05-04

結論

今は無理

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

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

くらいです。

以上。

18
19
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?