本資料はAngularDartの公式ガイドのなかのアーキテクチャのページの日本語訳です。不明な個所は原文を参照願います。
この概説で使われているサンプル・プログラムのコードはGitHubからダウンロードできます。またライブで試すこともできます。更に筆者による「プログラミング言語Dartの基礎」の添付資料のdart_code_samplesのappsにも同じものがangular2_architectureとして含めてありますので、すでにdart_code_samplesが自分のIDE上にある場合はそちらも使えます。
本資料を読む前に「プログラミング言語Dartの基礎」等でDart言語の基礎知識を理解しておくことをお勧めします。
またIDEとしてIntelliJを使う場合は「AngularDartの開発環境をつくる」の章、または「プログラミング言語Dartの基礎」の16.2節のなかの「AngularDartのパッケージ」を参照願います。
AngularはHTMLとJavaScriptに変換される言語(DartやTypeScript)でクライアント・アプリケーションを構築するのを支援するフレームワークです。
DartのためのAngularはangular2パッケージとして提供されており、これは(他の多くの言語と同様)Pubツールを使って利用できます。
Angularを使った場合、Angular化されたマークアップでHTMLテンプレート (HTML templates) を組みたててアプリケーションを書き、これらのテンプレートを扱うためのコンポネント・クラス (component classes) を書き、サービス (services) のなかにアプリケーション・ロジックを付加し、これらのコンポネントとサービスをモジュール (modules) とします。
このアプリケーションを走らせるときはトップのルート・モジュールをAngularのブートストラッパ (bootstrapper) に渡します。Angularはそれを引き受け、我々が用意した指示に従ってブラウザにそのアプリケーションの内容を表示し、ユーザの関わり合いに応答します。
むろんこれ以外に多くの事柄がありますが、それらの詳細はこのガイドの章の後の節の中で学ぶことになります。
|
このアーキテクチャ図はAngularアプリケーションの8つの主たる構成要素を示しています:
これらを順に説明していきます。
なお本章で参照しているコードはライブのサンプル(ソース・コードを見る)として利用できます。
Angularのアプリケーションはモジ
ュラー構成になっています。
一般的にあるアプリケーションは多くのモジュール (module) で構成されています。
典型的なモジュールは単一の目的のために特化したコードのかたまりです。モジュールはその中の値の何か(典型的にはクラスといった)を外部から使えるようにエクスポート (export) します(使う側は使いたいモジュールをインポート (import)します)。(訳者注:この節は完全にDart用に直されていません。exportはTypeScriptのキーワードです。Dartではアンダスコア(_)が最初に付く識別子を持ったオブジェクト以外はインポートすれば外部から可視になります)
Dartで違っているところ |
この解説では、モジュールという用語はライブラリあるいはパッケージといったDartのコンパイル単位のことを言います(もしDartファイルがlibrary またはpart ディレクティブを持っていないとしたら、そのファイル自身がライブラリであり、従ってコンパイル単位 (compilation unit) だということになります)。コンパイル単位の詳細は「Dart言語仕様書」または「プログラミング言語Dartの基礎」の「ライブラリとスクリプト」の章を見てください。 |
多分読者が最初に出くわすモジュールはあるコンポネントのクラスのモジュールです。このコンポネントは基本的なAngularのブロックのひとつであり、多くのコンポネントをこれから書くことになり、次の節でコンポネントに関し解説します。ここではコンポネント・クラスはあるモジュールとしてインポートされるたぐいのものだということを知っておれば十分です。
殆どのアプリケーションはひとつのAppComponent を持っています。これはそのアプリケーションのトップ・レベルのコンポネントで、慣例としてapp_component.dartという名前のファイルが使われています。そのようなファイルの中身を見ると、次のような宣言があることが判ります。
|
|
Dartで違っているところ |
TypeScriptと違って、Dartのライブラリはそのパブリック (public) な名前空間のすべての名前と宣言を常にエクスポートしており、明示的なエクスポート修飾詞は不必要です。 あるモジュールがある宣言をエクスポートしているということは、その宣言がパブリックだということを意味します。名前空間に関するより詳細は「Dart言語仕様書」または「プログラミング言語Dartの基礎」の「エクスポート」の記述を読んでください。 |
AppComponentへの参照が必要な場合は、次のようにインポートします:
|
|
Angularはangular2パッケージの中のライブラリの集まりとして提供されています。
各Angularライブラリの名前は先頭に angular2が付きます。
angular2/core は主要なAngularライブラリで、そこから必要とする殆どを得ることになります。
Angularライブラリには他にもangular2/common とangular2/router といったような重要なライブラリがあります。我々はあるAngularライブラリから必要とするものを次に示すようにインポートすることになります。
|
|
ここでのポイントは:
Angularアプリケーションはモジュールたちで構成されています。
モジュールたちはクラス、関数、変数といったものをライブラリやパッケージとしたもので、それを他のモジュールたちがインポートします。
自分のアプリケーションをモジュールたちの集まりとして書き、他のモジュールたちが各モジュールをインポートするようにすることをお勧めします。
おそらくは我々が書く最初のモジュールは、あるコンポネントを宣言するものになります。
コンポネントはビュー
(view)
と呼ぶスクリーン上の実際の置き場所への貼り付けを制御します。この章のアプリケーションの核になるものはナビゲーションのリンク、英雄たち(heroes)のリスト、heroのエディタ等々で、これらのビューの総てがコンポネントたちで制御されています。
あるコンポネントのアプリケーション・ロジック(そのビューをサポートすること)はあるクラス内で定義します。このクラスはプロパティとメソッドのAPIを介してそのビューとかかわりあいます。
例えばHeroListComponentはあるサービスで取得したヒーローたち(heroes)のリストを返す
heroesというプロパティを有します。selectHero()
メソッドはユーザがそのリストからあるヒーローを選択するのにクリックしたときに
selectedHero
という属性をセットします。その場合のコンポネントは次のようなクラスになります:
|
|
Angularはユーザがそのアプリケーション内でかかわりあう中でコンポネントたちを生成、更新、及び削除します。アプリケーション開発者はオプショナルなライフサイクル・フック
(lifecycle
hooks)を介して、上のコードのなかで宣言されているngOnInit()
のように、このライフサイクルの各状態でのアクションを用意することができます。
このコンポネントのコンストラクタを誰が呼び出しているのか?あるいは誰がこのサービス・パラメタを用意しているのか気になるかと思います。当面は必要になったときにAngularがこのコンストラクタを呼び、しかるべき HeroServiceを提供するのだと信じてください。
我々はあるコンポネントのビューを、コンポネントと対になるテンプレートで定義します。テンプレートはHTMLの形式で、Angularに対しそのコンポネントをどのように表示するのかを知らせます。
テンプレートは大抵通常のHTMLのように見えますがちょっと変わったものになっています。以下はHeroListComponent
のテンプレートです:
|
<h2>Hero List</h2> <p><i>Pick a hero from the list</i></p> <ul> <li *ngFor="let hero of heroes" (click)="selectHero(hero)"> {{hero.name}} </li> </ul> |
このテンプレートは<h2>
と<p>
のようなHTML要素が使われています。しかし*ngFor
,
{{hero.name}
}
,
(click)
,
[hero]
,
及び<hero-detail>
はなんでしょうか?
これはAngularのテンプレート構文 (template syntax)の例です。読者はそのうちこの構文に慣れ、好きになると思います。まずこれの説明から始めます。
その前に最後の行に注目願います。<hero-detail>
タグはHeroDetailComponent
を表現しているカスタム要素です。
このHeroDetailComponent
は我々が調べてきたHeroListComponent
とは別のコンポネントです。HeroDetailComponent
は(コードは示してありません)ユーザがHeroListComponent
で表示されたリストから選択した特定のヒーローに関する内容を表示するものです。HeroDetailComponent
はHeroListComponent
のチャイルドです。
<hero-detail>
は
本来のHTML要素たちとうまく適合していることに注意してください。これからは同じレイアウトのなかで本来のHTMLと自分たちのカスタムの要素たちをミックスさせてゆくことになります。
このように、自分たちの特徴的なアプリケーションを構築するのに複雑なコンポネントのツリーを組み立ててゆきます。
テンプレートはコンポネントと対になるものですが、なにもDartコードの'.dart'ファイルと別の'.html'ファイルに置かれる必要はありません。次に説明する@Componentアノテーションのなかのtemplateプロパティとして.dartファイルの中に記述することも可能です。我々のアプリケーションのsales_tax_component.dartがその例です。
メタデータはAngularに対しあるクラスをどのように処理するかを知らせます。
先ほどのHeroListComponent
のコードをみると、単一のクラスのみが存在しています。このクラス内にはフレームワークが使われているという証拠はなく、”Angular”という字句もありません。
実際のところこれは単にクラスです。Angularに対しそれを知らせるまではコンポネントではありません。
Angularに対しHeroListComponent
がコンポネントであることは、そのクラスに対しメタデータ
(metadata)を付すことで知らせます。
Dartではアノテーション
(annotation)を使ってメタデータを付します。以下はHeroListComponent
に対する幾つかのメタデータです:
|
|
ここでは@Component
アノテーションがあり、直下のクラスがコンポネント・クラスであることを指定します。
アノテーションは設定パラメタたちを持ちます。@Component
アノテーションはAngularがそのコンポネントとビューを生成し表示するのに必要な情報を与えるためにパラメタたちを使います。
以下は@Component
パラメタたちの幾つかを示してあります:
selector
:
親のHTMLの中の<hero-list>
タグが見つかったところにこのコンポネントのインスタンスを生成し挿入するようAngularに知らせるCSSセレクタです。例えばあるアプリケーションのHTMLが<hero-list></hero-list>
を含んでいた時は、Angularはこれらのタグ間にHeroListComponent
のインスタンスのビューを挿入します。
templateUrl
:
ひとつ前に示したこのコンポネントのテンプレートの場所です。
Directives
:
このテンプレートが必要とするコンポネントたちまたはディレクティブたちのリスト。上のテンプレートでは<hero-detail>
タグ間で示された場所にAngularがHeroDetailComponent
を挿入することを想定しています。Angularはこのdirectives
リストの中でHeroDetailComponent
が記されている場合に限り想定された通り処理します。
providers:
このコンポネントが必要とするサービスたちのための依存性注入プロバイダ
(dependency
injection
probiders)のリストです。自分たちのコンポネントのコンストラクタがを必要としているかを知らせるひとつの手段です。これにより表示するためのヒーローたちのリストを取得できます。依存性注入に関しては後で説明します。
実行時にAngularは@Component
アノテーションで指定されたメタデータを読み出します。これによりAngularは「ちゃんとしたこと」ができる方法を知ることができます。
テンプレート、メタデータ、及びコンポネントが一緒になってあるビューを記述しています。
Angularの振る舞いを指定するのに他のメタデータ・アノテーションたちを同じやり方で適用します。@Injectable
, @Input
,
及び @Output
は最も一般的なアノテーションの幾つかで、これらは
Angular
の知識が深まるにつれマスターすることになります。
かいつまんでいえばAngularが何をするかを知るために自分たちのコードにメタデータを付加しなければならないということです。
フレームワークなしだと、HTMLコントロールにデータの値を入れ込み、ユーザの応答をアクションと値の更新に変えることを自分でやらねばなりません。そのようなプッシュプルのロジックを手作業で書くのは大変であり、エラーを起こしがちであり、読むのも大変です。これは経験を積んだ jQuery のプログラマたちが納得できることです。
Angularはデータ・バインドに対応しております。これはテンプレートのパーツたちとコンポネントのパーツたちを連携させるメカニズムです。我々はHTMLのテンプレートにバインドのためのマークアップを付加してAngularに対して双方をどのように結びつけるかを知らせます。
データ・バインドの構文には4つの形式があります。各形式にはこの図で矢印で示されているように方向(DOMへ、DOMから、あるいは双方向で)があります。
ここで使っているサンプルのテンプレートでは3つの形式が使われています:
|
|
{{hero.name}}
内挿
(interpolation)はこのコンポネントのhero.name
プロパティの値を<li>
タグ間に表示します。
[hero]
プロパティ・
バインド
(property
binding)は親のHeroListComponent
のselectedHero
の値を子のHeroDetailComponent
のhero
プロパティに渡します。
(click)
イベント・バインド
(event
binding)はユーザがあるヒーローの名前をクリックしたときにこのコンポネントのselectHero
メソッドを呼び出します。
双方向データ・バインド(Two-way
data
binding)は4番目の重要な形式で、ngModel
指令を使って単一の記述でプロパティとイベントのバインドを組み合わせたものです。HeroListComponent
テンプレートには双方向バインドはありませんが、以下にHeroDetailComponent
テンプレートからの例を示します:
|
|
双方向バインドではデータ・プロパティ値はプロパティ・バインド同様コンポネントから入力ボックスに流れます。ユーザによる変更はまたコンポネントに逆流し、イベント・バインドのようにプロパティを最新の値にセットします。
AngularはJavaScriptのイベント・サイクルあたり一回総てのデータ・バインドをそのアプリケーションのコンポネント・ツリーのルートから枝葉にわたって処理します。
詳細のすべては未だ解っていなくても、これらの例をみればデータ・バインドはテンプレートとコンポネント間の通信の中で重要な役割を果たしていることがはっきりすると思います。
データ・バインドはまた親と子のコンポネント間の通信にとっても重要です。
Angularのテンプレートは動的です。Angularがこれらを描写するときに、ディレクティブで与えられた指示に従ってそのDOMを変換します。
ディレクティブはディレクティブ・メタデータを持ったクラスです。Dartではそのクラスにメタデータを付すときは@Directive
アノテーションを適用します。
我々は既にコンポネントのディレクティブ形式を見てきています。コンポネントはテンプレートつきのディレクティブです; 実際@Component
アノテーションはテンプレートがもとになっている機能たちで拡張した@Directive
アノテーションです。
コンポネントは技術的にはディレクティブではありますが、コンポネントはAngularアプリケーションにとっては特に特徴的かつ核になっているので、このアーキテクチャ概説ではコンポネントとディレクティブを分けて説明しています。
ディレクティブにはその他の2つの形式が存在します:即ち構造 (structual)と属性(attribute)ディレクティブです。
これらのディレクティブは属性と同様要素タグの内側に、時には名前であるいはより多くは代入またはバインドのターゲットとして置かれます。
構造ディレクティブはDOMのなかの要素を付加、削除、および置き換えることでレイアウトを変えます。
今使っているサンプルでは2つの組み込み構造ディレクティブが使われています:
|
|
*ngFor
はAngularに対しheroes
リストの中でhero
あたりひとつの<li>
を取り出すことを告げます。
*ngIf
は選択されたhero
が存在するときに限りコンポネントを含めます。
Dartで違っているところ |
Dartでは、真である値のみがブール値は |
属性ディレクティブは既存のエレメントの見かけや振る舞いを変更します。テンプレートの中ではこれらは通常のHTML属性(従って名前)のように見えます。
双方向バインドを実装しているngModel
ディレクティブは属性ディレクティブの一例です。ngModel
はその表示値属性をセットし変更イベントに応答することで既存の要素(通常<input>
)の振る舞いを変更します。
|
|
Angularにはレイアウト構造を変更する(例えばngSwitch)またはDOM要素とコンポネントの様相を変える(例えばngStyle とngClass)のどちらかを指定する幾つかのディレクティブが用意されています。
無論、自分自身のディレクティブを書くことも可能です。HeroListComponent
のようなコンポネントはカスタムのディレクティブの一種です。
サービスは自分のアプリケーションが必要とする値、関数、あるいは機能のいずれかをカバーする幅広いカテゴリです。
殆どなんでもサービスになり得ます。一般的にはサービスは狭く、はっきりした目的を持ったクラスです。
例としては以下のようなものが挙げられます:
ログ・サービス(logging service)
データ・サービス(data service)
メッセージ・バス(message bus)
税金の計算(tax calculator)
アプリケーション設定(application configuration)
サービスに関してはAngularの限ったものはありません。Angular自身サービスに関する定義は持っていません。サービス・ベースのクラスはなく、サービスを登録する場所もありません。
とは言えAngularアプリケーションにとってサービスは不可欠です。
以下はブラウザのコンソールにログを表示するサービス・クラスの例です:
|
|
以下はheroesを取ってきてFutureの終了のなかでそれらを返すHeroService
です。
このHeroService
はL
ogger
サービスと、もう一つのサーバ通信の仕事を取り扱うBackendService
に依存しています。
|
|
サービスはどこにも存在します。
我々が作成するコンポネント・クラスはなるべく簡素なほうが好まれます。我々が作成するコンポネントはサーバからデータを取ってくることはせず、ユーザ入力を検証することもせず、またコンソールに直接ログを出力することもしません。そのようなタスクはサービスに委譲します。
あるコンポネントの仕事はユーザ経験を可能とすることで、それ以上ではありません。コンポネントはビュー(テンプレートによって描写される)とアプリケーション・ロジック(しばしばモデルの概念を持つものが含まれる)との間を仲介するものです。重要なものは総てサービスに委譲します。
Angularはこれらの原則を強いてはいません。3000行にもなるいっぱい詰まったコンポネントを書いたとてもAngularは文句を言いません。
Angularは自分たちのアプリケーション・ロジックをサービスたちに分解し易くし、それらのサービスを依存性注入(dependency injection)を介してコンポネントが使い易くすることで、これらの原則に従い易くしています。
依存物注入はあるクラスの新規インスタンスをそれが必要とする完全なかたちの依存物たちを持たせて供給する手段です。殆どの依存物はサービスになります。Angularは依存物注入を使ってそれらが必要とするサービスを持った新規のコンポネントを用意します。
Angularはコンストラクタのパラメタの型を見てそのコンポネントがどのサービスを必要としているかを知らせることができます。例えば、ここで使っているサンプルのHeroListComponent
はHeroService
を必要とします:
|
|
Angularがあるコンポネントを生成するとき、最初にインジェクタ (injector)にたいしそのコンポネントが必要なサービスたちを要求します。
インジェクタは自分が既に生成したサービス・インスタンスのコンテナを持っています。要求されたサービスのインスタンスがコンテナ内になかった場合は、インジェクタはそれを生成し、Angularにそのサービスを返す前にそれをコンテナに追加します。要求されたサービスのすべてが生成され、返されたら、Angularはそのコンポネントのコンストラクタをこれらのサービスを引数として呼び出すことが可能となります。これが依存物注入の意味する事柄です。
HeroService の注入はこんな形になります:
|
もしインジェクタがHeroService を持っていなかったら、どうやってそれを生成するかが判るのでしょうか?
簡単に言えばHeroServiceのプロバイダ (供給者:providers)を前もってinjectorに登録しておかねばなりません。プロバイダはあるサービスを生成あるいは返すことが可能なものであり、一般的にはそのサービスのクラス自身になります。
アプリケーションのコンポネントのツリーのどのレベルにおいてもプロバイダを登録できます。しばしばそれはそのアプリケーションのブート時にルートでなされ、そのサービスの同じインスタンスがどこからでも使えるようにします。
|
|
代替手段として、コンポネントのレベルで、@Component
メタデータのproviders属性のなかで登録できます。
|
|
あるコンポネントのレベルで登録するというもとは、そのコンポネントの各新規インスタンスを持ったそのサービスの新規インスタンスを取得するということです。
依存物注入のポイントは次のようになります:
インジェクタが主たるメカニズムです。
インジェクタはそれが生成したサービス・インスタンスたちのコンテナを維持します。
インジェクタはプロバイダから新規サービス・インスタンスを生成できます。
プロバイダはサービス生成のための秘訣です
プロバイダたちをインジェクタに登録します。
ここまでAngularアプリケーションの8つの主たる組み立てブロックに関してやや詳しく学習してきました:
これがAngularアプリケーションのなかで何をおいても基本となり、学習を進めるには十分です。しかしこれは必要とするあるいは知りたくなる総てが含まれているわけではありません。
以下にアルファベット順にその他の重要なAngularの機能とサービスを挙げておきます。これらの殆どはこのデベロッパーズ・ガイドの中で解説される予定です。
アニメーション(Animation): アニメーションのライブラリは、アニメーションの技術やCSSの深い知識がなくてもコンポネントの振る舞いを動画化できます。
ブートストラップ(Bootstrap): ルートのアプリケーション・コンポネントを設定及び起動させるメソッドです。
変化検出(Change detection): あるコンポネントのプロパティ値が変化したか、及びいつスクリーンを更新するかをどのようにAngularが判断するかを学びます。非同期動作を押さえ、その変化検出戦略を実行させるのにzoneをどのように使うかを学びます。
コンポネント・ルータ(Component router): コンポネント・ルータのサービスを使うと、ユーザはマルチ・スクリーンのアプリケーションを馴染みのあるURLを使ったウェブ・ブラウズのスタイルでナビゲートできます。
イベント(Events): DOMがイベントを生起します。従ってコンポネントとサービスもイベントを生起します。Angularはイベントのパブリッシとイベントへの加入(subscribing)のメカニズムを提供します。
フォーム(Forms): HTMLベースの検証とダーティ・チェッキング(dirty checking)を持った複雑なデータ・エントリのシナリオに対応します。
HTTP: データ取得、データ保存のためにサーバと通信し、HTTPクライアントでサーバ・サイドのアクションを呼び出します。
ライフサイクル・フック(Lifecycle hooks): ライフサイクル・フックを実装することであるコンポネントの生成から削除に至る主要な瞬間に関与できます。
パイプ(Pipes): 表示のための値を変換するサービスです。ユーザ経験を改善するためにテンプレートにパイプを置くことができます。この通貨パイプ式を考えてみましょう:
|
これは"42.33"という値を$42.33と表示します。
ルータ(Router): そのクライアント・アプリケーションのなかでページからページへナビゲートし、ブラウザから出てしまうことはありません。
テスト(Testing): Angularにはテストのライブラリが用意されており、Angularと関わりあいながらアプリケーションの部品上でユニット・テストができます。
以下は読者がこの解説で使われているarchitectureアプリケーションをIDE上で実際に眺めかつ触れてみることで、上記の構成要素たちを理解してもらうよう訳者が追加しました。
Angularをより深く理解するには、本ガイドの残りの章を読んでいただく必要があります。
IDEとしてIntelliJを使う場合は「AngularDartの開発環境をつくる」の章、または「プログラミング言語Dartの基礎」の16.2節のなかの「AngularDartのパッケージ」を参照願います。
最初に「クイック・スタート」の章の「IntelliJでこのアプリを開いて見てみよう」の節に従って、このアプリケーションをIDE上で開いてみましょう。
このアプリケーション(Pub get実行後)をIDEのプロジェクト・ビューでみると次のような構成になっていることが判ります。
総てのAngularアプリケーションは2つのフォルダで構成されています。
|
web:このフォルダの中の3つのファイルはこのアプリケーションの入口とも言え、ブラウザが直接アクセスします。但しDartのVMが実装されていない通常のブラウザに対しては、Pub buildはmain.dartをJavaScript変換したmain.dart.jsを用意します。
lib:拡張子が.dartのファイルはいわゆるライブラリで、他のDartコードによってインポートされます。ここにはサービスの.dartファイル、コンポネントの.dartファイル、これと対になるコンポネントの.htmlファイル、その他のDartライブラリ・ファイルが置かれます。
アプリケーション開発者はこれらのフォルダ内に必要なコードを収容します。この例で分かるように、自分のアプリケーションをライブラリたちの集まりとして書くので、フォルダlibには多くのファイルが含まれることになります。大きなアプリケーションでは更にサブフォルダを用意して、より構造化します。
フォルダlibの中は以下の12個のファイルからなる構成となっています:
コンポネント |
|||
Dartコード |
テンプレート |
依存するサービス |
記 |
app_component.dart |
@Componentアノテーションの中のtemplate:プロパティとして |
|
トップ・レベルのコンポネント |
hero_list_component.dart |
hero_list_component.html |
HeroService |
app.componentの子のルート・レベル |
sales_tax_component.dart |
@Componentアノテーションの中のtemplate:プロパティとして |
SalesTaxService TaxRateService |
app.componentの子のルート・レベル |
hero_detail_component.dart |
hero_detail_component.html |
|
hero_list_componentの子のレベル |
サービス |
||
Dartコード |
クラス |
記 |
backend_service.dart |
BackendService |
HeroServiceのコンストラクタで呼ばれる |
helo_service.dart |
HeroService |
ヒーロたちのリストを取得するメソッドgetHeroesを持つサービスでBackendServiceとLoggerに依存 |
sales_tax_service.dart |
SalesTaxService |
Stringまたはnumのオブジェクト売上税金額を得るメソッドgetVATを持つサービスでTaxRateServiceに依存 |
tax_rate_service.dart |
TaxRateService |
税率を取得するメソッドgetRateを持つサービス |
logger_service.dart |
Logger |
ブラウザのコンソールにログ、エラー、警告を出力するメソッドたちを有するクラス |
その他 |
||
Dartコード |
クラス |
記 |
Hero.dart |
Hero |
Heroのクラス定義 |
AngularのアプリケーションはAppComponentをトップとしたコンポネントたちのトリーで構成されます。architectureアプリケーションも4つのコンポネントで構成されています:
ビュー上では下図のように3つのコンポネントで構成されています:
hero_list_componentはヒーローたちのリストを表示し、またユーザのクリックによりhero_detail_componentによる表示を付加します
hero_detail_componentはhero_list_componentの子で、原文では「heroエディタ」と称しています。まず上のリストのどれかをユーザがクリックするとその詳細が表示されます。次にNameとPowerはユーザが変更できます。その変更は上のリストにも反映されます
sales_tax_componentは上のコンポネントとは独立しています。ユーザがAmount(金額)を数字で入力すると売上税額が表示されます
例えば、helo_list_componentを見てみましょう。このコンポネントのDartコードは次のようになっています:
|
|
コンポネントのところで説明してあるように、このファイルはHeroListComponentというクラス定義とそれに対する@Componentアノテーションで構成されています。
@Componentアノテーションはこれに続くクラスのデコレータで、Angularにたいし直後に続くクラスがコンポネントであることを知らせ、またこのコンポネントをいつインスタンス化するか、どのプロパティとホスト・リスナにバインドするかなどを指定します:
selector:このディレクティブのインスタンス化のトリガとなるCSSセレクタで、ここでは親のAppComponentの中のテンプレートの中に<hero-list></hero-list>というタグ区間があります
directives:このコンポネントに含まれるディレクティブのリストで、ここではHeroDetailComponentコンポネント
providers:このディレクティブから可視な注入可能オブジェクトのリストで、ここではHeroServiceのオブジェクト
HeroListComponentクラスは以下のものでできています:
heroes プロパティ:ヒーローたちのListで、ライフサイクル・フックngOnInit()メソッドでセットされます
selectedHero プロパティ:ユーザ・クリックで選択されたヒーローのオブジェクト
HeroListComponentコンストラクタ:Angularがインスタンス化します
selectHeroメソッド:selectedHero プロパティをセットする
あるコンポネントがインスタンス化されると、
AngularはそのコンポネントのためのシャドウDOMを生成し
そのシャドウDOMのなかに選択されたテンプレートをロードし
providersで設定された注入可能なすべてのオブジェクトたちを生成します。
総てのテンプレートのなかの式と文がそのコンポネント・インスタンスに対して計算されます。
テンプレート、メタデータ、及びコンポネントが一緒になってあるビューを記述しています。テンプレートはHTMLファイルとして、あるいは@componentアノテーションのtemplate:プロパティとして作成できます。
Angularはユーザが何を見また何ができるかを、Componentクラスのインスタンス(コンポネント)とユーザが面するテンプレートを介して実現します。コンポネント/テンプレートの二元性はMVC(model-view-controller)あるいはMVVM(model-view-viewmodel)で馴染みのものです。Angularではコンポネントは controller/viewmodelとして、テンプレートがviewとして機能します。
|
<h2>Hero List</h2> <p><i>Pick a hero from the list</i></p> <ul> <li *ngFor="let hero of heroes" (click)="selectHero(hero)"> {{hero.name}} </li> </ul> <hero-detail *ngIf="selectedHero != null" [hero]="selectedHero"></hero-detail> |
これはHeroListComponentのテンプレートです。このテンプレートには通常のHTMLとは異なった行が存在します:
順序なしリスト・アイテム・タグ<li>の中には*ngFor="let hero of heroes"が存在している:*ngForはAngularの繰り返しディレクティブで、heroesリストのすべてのアイテムheroに対しこのタグに続くテキストを表示します。この変数heroはこのコンテクストのなかで使われています
(click)はこのアイテム上でのクリック・イベントがバインドのターゲット(イベント・バインディング)であることを示します。ここではselectedHeroをセットするselectHero(hero)メソッドを呼び出しており、これが<hero-detail>タグ要素で使われています
式hero.nameを2重波括弧で囲んだ部分は文字列内挿(インターポレーション)でありこの式の値が文字列として表示されます
<hero-detail>はHTMLのタグではなく、HeroDetailComponentが埋める区間であることを示します
*ngIfディレクティブはそれに続く式がteueであるときにこの要素(及び副要素たち)をDOMに付加します
[hero]=はプロパティ・バインドで、このコンポネントのプロパティheroにselectedHeroの値をセットしてこの区間を表示させます
詳細はテンプレートの構文を参照してください。
データ・バインディングはテンプレートのパーツたちとコンポネントのパーツたちを連携させるメカニズムです。我々はHTMLのテンプレートにバインドのためのマークアップを付加してAngularに対して双方をどのように結びつけるかを知らせます。
architectureアプリケーションでは3つのコンポネントのテンプレート上で以下のようなバインディングが使われています。
HeloListComponent |
||
方向 コンポネント--DOM |
タイプ |
記述 |
→ |
内挿 |
<li>{{hero.name}}</li>
|
→ |
プロパティ |
<hero-detail [hero]="selectedHero"></hero-detail>
|
← |
イベント |
<li (click)="selectHero(hero)"></li>
|
2番目の<hero-detail>タグは、次のクリック・イベントが呼んだselectHero(hero)で得られたselectedHeroオブジェクトをHeroDetailComponentのheroプロパティにセットします。
HeloDetailComponent |
||
方向 コンポネント--DOM |
タイプ |
記述 |
→ |
内挿 |
<h4>{{hero.name}} Detail</h4>
|
→ |
内挿 |
<div>Id: {{hero.id}}</div>
|
⇔ |
双方向 |
<input [(ngModel)]="hero.name">
|
⇔ |
双方向 |
<input [(ngModel)]="hero.power">
|
下の二つの双方向バインドはngModelディレクティブを使って単一の記述でプロパティとイベントのバインドを組み合わせたものです。入力タグのなかで、表示されるhero.nameやhero.powerはユーザがこれを変えるたびにこれらのプロパティ値を変更します。双方向バインディング(two-way binding)は入力やフォーム入力タグの中で利用されます。
SalesTaxComponent |
||
方向 コンポネント--DOM |
タイプ |
記述 |
→ |
内挿 |
{{ getTax(amountBox.value) | currency:'USD':true:'1.2-2' }}
|
この内挿の記述はやや複雑です。"|”はDartの演算子とは違い、パイプ演算子(pipe operator)といいます。詳細はテンプレート構文の章の式演算子を読んでください。
サービスは値、関数、あるいは機能を提供するクラスで、@Injectable()アノテーションが付されています。Angularは必要な時にそのオブジェクトをコンポネントに渡します。その為の依存物注入(dependency injection)はアプリケーション開発には重要なデザイン・パタンです。その詳細は追って解説いたします。
プロバイダ(provider)は依存物の新規インスタンスを生成します。依存物たちを使うコンポネントのクラスは、@componentアノテーションのproviders:プロパティでそれをListの型で宣言します。
architectureアプリケーションでは6つのサービスが存在しますが、それらの受けては次のようになっています:
サービス |
受けて |
記述 |
backend_service |
helo_service |
HeroService(this._logger, this._backendService);
|
logger_service |
||
helo_service |
helo_list_component |
providers: const [HeroService] HeroListComponent(this._heroService); |
tax_rate_service |
sales_tax_component |
providers: const [SalesTaxService, TaxRateService] SalesTaxComponent(this._salesTaxService) {} |
sales_tax_service |
hero_serviceはコンポネントではないので、providers:プロパティはありませんが、コンストラクタの中で2つのサービスのオブジェクトをプライベートな変数として保持します。
即ちサービス注入を受ける側はコンストラクタの中でそのサービスを外部からは見えない形でそれらのサービスを保持し、外部からは必要なメソッドでそれらのサービスのデータを使用します。コンストラクタを見ればそのコンポネントやサービスがどのサービスでできているかが一目瞭然です。このパタンはテストが容易という利点もあります。正式のサービスではなくてモックアップのサービスを使うことで、先行してテストを行うことも可能になります。