Anuglarの@Outputを使いこなしているでしょうか。@Outputが理解できれば、Angularの初級レベルは脱したと言えるくらい最初は理解しにくいものかと思います。私は実際にAngularを使い始めて1ヶ月くらい過ぎてから、やっと@Outputの使い方を理解しました。と言うのも私はもともとReactとReduxを使った開発をメインで行っていたことから、始めて関わったAngular案件でも@ngrx/storeを導入したため、それほど@Outputの必要性を感じていかなったし、いまいち使いどころがピンときていませんでした。しかし、実際に使ってみると、これがなかなか強力な役割を果たすということがやっとわかりました。今回はこのAnuglarの@Outputについて紹介したいと思います(昨年Qiitaで書いた記事の転載となります)。
はじめに – @Outputは何のために使うか
Angularでは、親コンポーネントから子コンポーネントに値を渡す場合は、まず親コンポーネント側でプロパティバインディング([]
)に値を渡し、子コンポーネント側で@Input
を通して受け取ります。@Output
はこの逆を行うものと考えてもらえれば良いでしょう。つまり、子コンポーネントから親コンポーネントに値を渡したい場合や、また子コンポーネントで発生したアクションを親コンポーネントに伝えたい場合に使います。
@Output
を使うことで、アプリ全体で必要となる処理を親コンポーネントに集約できるようになり、子コンポーネント側には余計な処理を書かなくて済むようになります。使いこなせるようになると、手放せなくなる便利な機能です。
例えば、Reactであれば、親コンポーネント(classコンポーネント)で管理しているstate
を更新するにはReact.Component
のsetState
メソッドを呼び出すことになっています。このsetState
を呼び出すメソッドを親コンポーネントで定義され、子コンポーネントはこれをprops
を通して受け取り、呼び出すことで親コンポーネントのstate
の更新を可能にしています。
Reactでは、以下のように親コンポーネントのメソッドを子コンポーネントで呼び出し、state
を更新します。
それ故にReactでは、親コンポーネントにロジックを集約し、子コンポーネントは状態を持たないただの関数でコンポーネントで構築することができるわけです。Angularの@Output
は、まさにこのReact的なデータフローの仕組みをAngularで実現させるものと言うこともできるでしょう。
Reactのこの辺りの仕組みは、以下の公式ドキュメントに詳しく書いてあります。
Angularの@Output
も、実際にコードを書いてみると何が良いかがよくわかるかと思います。これより以下に実例を元に説明していきます。(例ではしれっと@ngrx/storeを使っていますが、そこはあまり難しく考えないでください。)
@Outputを使った例
まず親コンポーネントとなるParentComponent
に状態となるhogeState$
を更新するためのhandleSendValue
メソッドを定義しています。あえてReact的に言うと、このhandleSendValue
メソッドを子コンポーネントとなるchild-component
要素にsendValue
として渡しています。Angular的に言うと、カスタムイベントであるsendValue
が子コンポーネントのアクション(emit)により発火した際にイベントハンドラのコールバック関数として定義したhandleSendValue
メソッドが呼ばれるようになっています。
以下は子コンポーネントのChildComponent
です。ここもあえてReact的に言うと、親コンポーネントから渡されたsendValue
を@Output()
を通して受け取ります。Angular的に言うと、sendValue
をイベントを発生させるエミッターとして定義します。その際に@Output()
でデコレートすることで、親コンポーネントに通知ができるようになります。
Auglarの仕組みとしては、@Output()
でデコレートしたsendValue
のemit
メソッドを呼び出すことで、親コンポーネントにイベントが発火したことを通知することができるようになります。その際に親コンポーネントに渡したい値をemit
メソッドの引数に指定します。
通知を受け取った親コンポーネントは、イベントを受け取ったsendValue
のコールバック関数として定義したhandleSendValue
メソッドを呼び出します。またhandleSendValue
メソッドの引数を通してemit
メソッドに指定した値(ここでの例では{ value: 'hoge' }
)を受け取ります。
上記で紹介したReactの動きにもかなり似ていますね。このように@Output()
を使うと、親コンポーネントに処理を集約しつつ、子から親へのデータの受け渡しができるようになります。
@Outputを使わなかった場合
ちなみに、@Output
を使わなかった場合の例は以下となります。親コンポーネントの状態hogeState$
を更新するメソッドを子コンポーネント側に定義しています。
子コンポーネント側で直接親コンポーネントの状態を更新するメソッドを呼び出しています。
正直、例を挙げておいて言うのも何ですが、これくらい単純なものだとよく違いがわからないですかね。でも、アプリケーションの規模が大きくなると管理が大変になるのは、容易に想像がつくことでしょう。親コンポーネントに必要な処理をまとめておき、必要に応じて子コンポーネントからイベントを投げて、親コンポーネント側で処理を実行するようにするとアプリの構造もシンプルになり、同時にメンテナブルにもなります。
@Outputの使い方
ここから@Output
の使い方を紹介していきます。まず@Output
については、デコレータであり、カスタムイベントを作るためのものとなります。詳細は以下の公式のドキュメントに詳しく書いてあります。
使い方としては、まずは、必要なモジュールを@angular/core
から読み込みます。@Output
を使う場合は、Output
とEventEmitter
が必要となります。
例えば、hogeEvent
としてカスタムイベントを作ります。イベント発火時に、emitする値の型を<T>
で定義します。
作ったイベントのemit()
メソッドを呼び出すとイベントが発火します。引数にはイベントハンドラーのコールバック関数に渡したい値を指定してもよいです。
@Output
を定義しているコンポーネントを取り込む際にテンプレート上で、(click)
などと同じようにイベントバインディングでイベントハンドリングすることで、hogeEvent.emit()
が呼ばれた時に、handleHogeEvent($event)
を呼び出すことができるようになります。emit(event)
の引数で渡された値は、コールバック関数であるhandleHogeEvent($event)
の引数$event
で受け取れます。
@output
を使う場合は、ここで紹介したパターンさえ覚えておけば何とかなります。
@Outputのユニットテストについて
最後に@Output
のユニットテストについて軽く触れておきます。@Output
関連のテストコードを書く場合は以下のようになるかと思います。上記の例のテストコード例をテストケースのみとなりますが、以下に紹介します。
実際に@Output
を定義している子コンポーネント側のテストコードは以下となります。ボタンが押された時にhandleClick
が呼び出され、sendValue
イベントが発火するかのテストとなっています。ポイントとしては、sendValue
イベントの通知をsubscribe
で受け取っているところでしょうか。
以下は親コンポーネント側のテストコードとなります。こちらでは、sendValue
イベントが発火した時にhandleSendValue
が呼び出されるかのテストとなっています。
@Output
のユニットテストについては公式ドキュメントの以下の箇所に実例が載っているので、こちらも見てらもうと良いでしょう。
まとめ
以上、Angularの@output
についていろいろ書いてみましたが、ちゃんと使い方さえ覚えてしまえば簡単に使えるものだと思います。しかもその効果はかなり協力なので、複雑になりがちなAngularの処理をそれなりにシンプルにすることができるのではないかと思います。慣れるまでが大変かと思いますが、なんとなく使い続けていくうちにわかるようになってくるかと思います。できる限り意識して使うようにしていくと良いでしょう。
最後にAngularの書籍を紹介しておきます。私は昨年の9月からAngular案件に関わるようになりまして、その時にAngularを覚えるために使った本です。体系的にまとめられていて、内容も良いと思っています。Amazonでの評価も高くおすすめです。
コメント