Chapter 03無料公開

コンポーネント

lacolaco
lacolaco
2022.06.08に更新

この章ではAngularの語彙としての「コンポーネント」について解説する。公式ドキュメントの用語集では、コンポーネントは次のように説明されている。

A class with the @Component() decorator that associates it with a companion template.

つまり、コンポーネントとは @Component() デコレーターが付与され、対応するテンプレートを備えたクラスである。端的にいえばそれだけだが、ではその @Component() デコレーターはコンポーネントとその他のクラスとの間にどのような違いをもたらすのだろうか。

本書ではコンポーネントの本質的な役割を2つに分けて説明する。ひとつがビューファクトリーとしてのコンポーネントであり、もうひとつがビューモデルとしてのコンポーネントである。

ビューファクトリーとしてのコンポーネント

コンポーネントの本質的な役割のひとつは、コンポーネントが必ず備えるテンプレートによってビューを生成できることだ。

ビューで述べたとおり、コンポーネントはコンポーネントビューを生成する。コンポーネントビューはコンポーネントクラスとそのテンプレートによって定義される。また、コンポーネントはテンプレート中で他のコンポーネントを呼び出すことで階層構造をつくる。アプリケーションの起動 (bootstrap) に使われるルートコンポーネントを起点に作られる階層構造はコンポーネントツリーと呼ばれる。

ただ、テンプレートをもとにビューを生成する処理は、開発者の目の届くところには書かれていない。ビューファクトリーとしてのコンポーネントのコードは、アプリケーションをビルドする際にAngularのコンパイラーによって機械的に生成されている。内部的にはコンポーネントクラスの静的フィールドとして生成されているが、そのことを開発者が意識する必要はほとんどない。覚えておくべき重要なことは、@Component() デコレーターに渡すメタデータはビューファクトリーとしてのコードを生成するために使われているということだ。

ビューファクトリーとしてのコンポーネントの実体は、@Component() デコレーターから作られた生成コードである。それはつまり、開発者が書いたコンポーネントクラスのコードはそれ以外の役割を持っているということだ。それが次のビューモデルとしてのコンポーネントという役割である。

ビューモデルとしてのコンポーネント

ビューファクトリーとしてのコンポーネントはコンポーネントクラスそのもの(正確には静的フィールド)だが、ビューモデルとしてのコンポーネントの実体は、コンポーネントクラスのインスタンスオブジェクトである。つまり、あるコンポーネントについてビューファクトリーとしての実体はひとつしかないが、ビューモデルとしての実体はコンポーネントが呼び出された数だけ存在する。テンプレート中でコンポーネントが呼び出されると、コンポーネントクラスのインスタンスと共にビューが生成され対応付けられる。したがって、ビューファクトリーとビューは一対多の関係であり、ビューとビューモデルが一対一の関係である。

ビューモデルとしてのコンポーネントの主な役割は、ビューにデータを保持したプロパティとイベント発火時に呼び出し可能なメソッドを提供すること、つまり、テンプレートとバインディングされることである。テンプレート内でバインディングできるデータがコンポーネントクラスのフィールドに限られるのはこのためである。コンポーネントビューの振る舞いをコントロールできるのは、そのビューと対応付けられたビューモデルとしてのコンポーネントクラスのインスタンスだけだ。

ビューのライフサイクルに対してフックすることができるのも、ビューモデルとしてのコンポーネントの機能である。OnInitOnDestroy といったコンポーネントのライフサイクルフックメソッドは、そのコンポーネントに紐付いたビューのライフサイクルに合わせて任意の処理を追加するためのインターフェースである。ビューから発火されるイベントをコンポーネントがハンドルするという構図だけを見れば、ライフサイクルフックとイベントバインディングの間に大きな違いはない。

ビューモデルとしてのコンポーネントは、ビューファクトリーとしてのコンポーネントが生成したビューと一対一で対応付けられる。そして、テンプレートに記述したバインディング定義にしたがってビューとビューモデルの状態を同期するのが、コンポーネントの変更検知である。変更検知とはまさに「ビューモデルの変更を検知してビューに反映する」ことである。

コンポーネントはディレクティブの特殊な形である

コンポーネントはディレクティブでもある。ディレクティブはAngularが構築するビューに何かしらの作用を与えるクラスだが、その中でもビューを生成することができる特殊なディレクティブがコンポーネントである。なぜビューを生成できるかといえば、コンポーネントがテンプレートを持つからである。つまり、コンポーネントとはテンプレートを持つディレクティブである

あらゆるディレクティブはひとつのDOM要素に紐付く形で機能する。このDOM要素をディレクティブのホスト要素と呼び、コンポーネントも例外ではない。ディレクティブとしてのコンポーネントのホスト要素は、そのコンポーネント自身が生成するDOM要素である。一方で、コンポーネント以外のテンプレートを持たないディレクティブは、すでに存在するDOM要素に対してアタッチする形でホスト要素と紐づく。ディレクティブのクラスだけがインジェクトできる ElementRef は、そのディレクティブのホスト要素の参照を返すAPIである。このようなディレクティブ特有の機能は、コンポーネントにおいても例外なく利用可能である。

言いかえれば、ディレクティブにはないコンポーネント特有の性質というのは、テンプレートとそれに紐付くコンポーネントビューに関連するものだけである。具体的には、CSSのカプセル化やアニメーション、変更検知など、 @Component() デコレーターにあって @Directive() デコレーターにないプロパティがそれにあたる。それ以外はすべてディレクティブとしての性質だ。

したがって、コンポーネントについて深く学ぼうとするなら、ディレクティブを理解することから始めなければならない。