複数コンポーネントを利用するサンプル
コンポーネントとモジュールの記述方法を、サンプルコードで説明します。今回はWebページに複数のコンポーネントを追加して、コンポーネント間でデータの送受信をするところまでを説明します。これらのコンポーネントをモジュールに切り出す方法は次回記事で取り上げます。
もとにするサンプル(angular2-001-component1)は、コンポーネント内に定義されたスマートフォン情報配列の内容を、ngForディレクティブで繰り返し表示します。詳細はダウンロードできるサンプルコードを参照してください。
コンポーネント1:詳細表示コンポーネント
まず、別コンポーネントに値を渡す例として、リストのリンクをクリックして選択したスマートフォンの詳細を図4のように表示するコンポーネント(赤背景の部分)を追加します。
ルートコンポーネント(AppComponent)のテンプレートをリスト4のように編集します。
<ul>
<li *ngFor="let phone of phones">
<!-- 一覧表示にリンクを設定 ...(1) -->
<a href="#" (click)="onClick(phone)">{{phone.name}} ({{phone.vendor}})</a>
</li>
</ul>
<!-- 詳細表示 ...(2) -->
<phone-detail [phone]="selectedPhone"></phone-detail>
(1)が一覧表示部です。ngForディレクティブで繰り返し表示される部分にリンクを設定して、クリック時にonClickイベントハンドラを実行するようにします。(2)はこれから作る詳細表示コンポーネントを表示する部分で、「[phone]="selectedPhone"」は、phone属性経由で変数selectedPhoneを詳細表示コンポーネントに渡すことを表します。
リンククリック時の処理onClickはリスト5のようになります。選択されたスマートフォンに対応するオブジェクトphoneを、コンポーネントの変数selectedPhoneに格納します。
onClick(phone: Phone) {
this.selectedPhone = phone;
}
次に、リスト4(2)のphone-detail要素に表示される詳細表示コンポーネントをリスト6のように定義します。
// Angular 2のコンポーネント参照 ...(1)
import { Component, Input } from "@angular/core";
// Phoneクラスの参照 ...(2)
import { Phone } from "./phone";
// コンポーネントのメタデータ定義 ...(3)
@Component({
selector: "phone-detail",
styles: ["div.component { background:#ffd5d5}"],
template: `
<div class="component" *ngIf="phone">
<h4>【PhoneDetailComponent: 詳細表示】</h4>
<ul>
<li>名前: {{phone.name}}</li>
<li>会社: {{phone.vendor}}</li>
<li>発売: {{phone.delivery}}</li>
</ul>
`
})
// コンポーネントクラス定義 ...(4)
export class PhoneDetailComponent {
// phone-detailディレクティブの「phone」属性でPhoneオブジェクトを指定する記述
@Input("phone") phone: Phone;
}
(1)はAngular 2のコンポーネント参照、(2)はスマートフォン情報を格納するPhoneクラス定義の参照です。(3)のメタデータ定義で、selector属性にHTMLタグ名(phone-detail)、template属性にテンプレートを、それぞれ設定しています。styles属性は必須ではありませんが、ここではコンポーネントの表示範囲がわかりやすいように背景色を設定しています。
(4)はコンポーネントクラスの定義です。(1)で参照した@Inputデコレーターを変数phoneの前に「@Input("phone")」のように記述すると、タグのphone属性に指定されたオブジェクトが取得できます。コンポーネント間で値を引き渡すという意味で、AngularJS 1のbindControllerと似た機能といえます。なお、属性名と変数名が同じ場合はリスト7のような省略記法が利用できます。
@Input() phone: Phone;
// 「@Input("phone") phone: Phone」と同じ意味
ルートコンポーネント側のリスト4(2)との組み合わせで、ルートコンポーネントで選択されたスマートフォン情報(selectedPhone)が、詳細表示コンポーネントの変数phoneに設定されます。設定されたphoneの内容は{{}}記述によるデータバインディングで画面に反映されます。
リスト4~6を実行してスマートフォンのリンクをクリックすると、phone-detail要素の場所に図4のように詳細表示コンポーネントが表示されます。
コンポーネント2:項目追加コンポーネント
次に、別コンポーネントから値を受け取る例として、スマートフォンの項目を新しくリストに追加する図5コンポーネント(青背景の部分)を定義していきます。
AppComponentのテンプレートに、項目追加コンポーネントの表示指定をリスト8のように追加します。
<!-- 新規入力 --> <phone-input (onSubmitted)="onSubmittedPhoneInput($event)"></phone-input>
リスト8のonSubmittedは、入力値が登録されたときに発生するイベントで、後述する詳細表示コンポーネントで定義します。詳細表示から渡されるイベント変数は$eventで参照されるので、それを引数にしてイベントハンドラ(onSubmittedPhoneInputメソッド)を実行します。
onSubmittedPhoneInputメソッドの実装はリスト9です。イベント引数として渡されるphoneオブジェクトを、自身が持つ配列phonesの末尾に追加します。
onSubmittedPhoneInput(phone: Phone) {
this.phones.push(phone);
}
次に項目追加コンポーネントをリスト10のように記述します。
// コンポーネント、クラスの参照 ...(1)
import { Component, EventEmitter, Output } from "@angular/core";
import { Phone } from "./phone";
// コンポーネントのメタデータ定義 ...(2)
@Component({
selector: "phone-input",
styles: ["div.component { background:#d7e3f4}"],
template: `
<div class="component">
<h4>【PhoneInputComponent: 項目追加】</h4>
<input type="text" placeholder="名前" [(ngModel)]="newPhone.name"><br/>
<input type="text" placeholder="会社" [(ngModel)]="newPhone.vendor"><br/>
<input type="text" placeholder="発売" [(ngModel)]="newPhone.delivery"><br/>
<button (click)="onClick();">追加</button>
</div>
`
})
// コンポーネントクラス定義
export class PhoneInputComponent {
// 追加するPhoneオブジェクト ...(3)
newPhone:Phone = new Phone();
// 追加時に発生させるイベント ...(4)
@Output("onSubmitted") onSubmitted = new EventEmitter<Phone>();
/**
* 追加ボタンクリック時のハンドラ ...(5)
*/
onClick() {
// newPhoneを引数にしてonSubmittedイベント発生
this.onSubmitted.emit(this.newPhone);
// newPhoneオブジェクトを再生成してフォームリセット ...(6)
this.newPhone = new Phone();
}
}
(1)はAngular 2コンポーネントとPhoneクラスの参照、(2)が項目追加コンポーネントのメタデータ定義です。フォームの入力内容を[(ngModel)]による双方向データバインディングでコンポーネントクラスの変数newPhone(3)と同期するようになっています。
(1)で参照したEventEmitterと@Outputデコレーターを用いて、(4)でルートコンポーネントへ伝達するイベントを定義しています。@Output("onSubmitted")は、ルートコンポーネントでonSubmittedイベントを発生させる意味です。また、EventEmitterで指定している<Phone>記述は、イベントに引き渡すパラメータ変数がPhoneクラスであることを表しています。なお、発生させるイベント名と変数名が同じ場合はリスト11のような省略記法が利用できます。
@Output() onSubmitted = new EventEmitter<Phone>();
//「@Output("onSubmitted") onSubmitted」と等価
(6)は、newPhoneに新しい(空の)Phoneオブジェクトを設定して、入力フォームの内容をリセットするための処理です。
テンプレートの確定ボタンがクリックされると、(5)のonClickメソッド内でonSubmited.emitメソッドが実行され、引数にnewPhoneを指定したonSubmittedイベントを発生させます。ルートコンポーネントでは、リスト8、9の実装でイベント引数からnewPhoneの情報を取り出してスマートフォンリストに追加します。
リスト8~10を実行すると、図5のように項目追加コンポーネントが表示され、入力した内容がスマートフォンリストに追加されます。
まとめ
本記事では、Angular 2でプログラムを分割するコンポーネントやモジュールについて説明し、コンポーネントの実装方法をサンプルで説明しました。コンポーネントやモジュールにより、影響範囲が局所化された効率的なコードを記述でき、共通の機能をモジュールにして部品化するなど、生産効率の向上が期待できます。
モジュールには今回説明したコンポーネントのほかに、共通処理などを提供する「サービス」と呼ばれる要素を含めることができます。次回は今回作成したコンポーネントをモジュールに切り出す方法を解説するとともに、サービスとそれを利用するためのDependency injection(依存性の注入)について説明します。
