第3章までに作成したプログラムではAppComponent
が全てを担っていました。このペースで機能を追加していくとメンテナンスが難しくなります。
そこで本章では、それぞれが決まったタスクもしくはワークフローをもった複数のコンポーネントに分割していきます。最終的にAppComponent
は子供のコンポーネントをもったシンプルなシェルになります。
本章ではヒーローの詳細を、再利用可能な別のコンポーネントにします。
ヒーローの詳細コンポーネントの作成
app
ディレクトリにhero-detail.component.ts
というファイルを作成します。このファイルに新たなコンポーネントであるHeroDetailComponent
を記述します。
HeroDetailComponent
を次のように記載してください:
import { Component } from '@angular/core'; @Component({ selector: 'hero-detail', }) export class HeroDetailComponent { }
コンポーネントを定義するには、必ずComponent
シンボルをインポートします。
@Component
デコレータは、コンポーネントにAngularのメタデータを提供します。CSSのセレクタ名であるhero-detail
は親のコンポーネントのテンプレートにおいてこのコンポーネントを特定するタグを指定しています。
コンポーネントクラスは必ずexport
します。理由は、他の場所において必ずimport
するからです。
ヒーローの詳細のテンプレート
ヒーローの詳細表示をHeroDetailComponent
に移動するには、AppComponent
の中のテンプレートの下の部分を切り取り、HeroDetailComponent
の@Component
メタデータにおけるtemplate
属性に設定します。
なお、HeroDetailComponent
は選択されたheroではなく特定のheroをもつので、selectedHeroと記載した箇所はheroに変更します。
hero-detail.component.ts
は次のようになるはずです:
import { Component } from '@angular/core'; @Component({ selector: 'hero-detail', template: ` <div *ngIf="hero"> <div> <input [(ngModel)]="hero.name"> </div> <div><label>id: </label>{{hero.id}}</div> <div><label>name: </label>{{hero.name}}</div> </div> ` }) export class HeroDetailComponent { }
hero属性の追加
HeroDetailComponent
のテンプレートは、コンポーネントのhero
属性にバインドします。よって、HeroDetailComponent
クラスに次のように属性を追加します:
hero: Hero;
さて、hero
はHero
のインスタンスです。Hero
クラスはapp.component.ts
に入っています。Angularのスタイルガイドは1つのファイルにつき1つのクラスを推奨しているので、Hero
クラスをapp.component.ts
からhero.ts
に分割します:
// hero.ts export class Hero { id: number; name: string; }
これでHero
クラスは自分自身のファイルに入っているのでAppComponent
とHeroDetailComponent
はインポートしなければなりません。app.component.ts
とhero-detail.component.ts
の最上段の近くに次のimport
を追加します:
import { Hero } from './hero';
hero属性は、入力属性です
本章の後半において、AppComponent
はselectedHero
をHeroDetailComponent
のhero
属性にバインドすることにより、子コンポーネントであるHeroDetailComponent
がどのヒーローを表示すべきかを指定します。バインドはこのようになります:
<hero-detail [hero]="seletedHero"></hero-detail>
hero
属性の周りを[ ]
で括るのは属性のバインド式の相手であることを示しています。この場合、相手となる属性が入力属性であることを宣言しなければなりません。そうしないと、Angularはバインドを拒否しエラーを出します。
まず、@angular/core
のインポート分を修正し、Input
シンボルを追加します:
import { Component, Input } from '@angular/core';
次に、@Input
デコレータを頭につけてhero
が入力属性であることを宣言します:
@Input() hero: Hero;
以上です。HeroDetailComponent
クラスにおける唯一の属性はhero
属性です:
export class HeroDetailComponent { @Input() hero: Hero; }
最終的なHeroDetailComponent
は次の通りです:
import { Component, Input } from '@angular/core'; import { Hero } from './hero'; @Component({ selector: 'hero-detail', template: ` <div *ngIf="hero"> <div> <input [(ngModel)]="hero.name"> </div> <div><label>id: </label>{{hero.id}}</div> <div><label>name: </label>{{hero.name}}</div> </div> ` }) export class HeroDetailComponent { @Input() hero: Hero; }
AppModuleにおいてHeroDetailComponentを宣言する
全てのコンポーネントは一つのNgModuleにおいて1回だけ宣言されなければなりません。
app.module.ts
を開き、参照できるようにHeroDetailComponent
をインポートします:
import { HeroDetailComponent } from './hero-detail.component';
そして、declarations
配列にHeroDetailComponent
を追加します:
declarations: [ AppComponent, HeroDetailComponent ],
この時点で、app.module.ts
は次のようになっているはずです:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule} from '@angular/forms'; import { AppComponent } from './app.component'; import { HeroDetailComponent } from './hero-detail.component'; @NgModule({ declarations: [ AppComponent, HeroDetailComponent ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ~
AppComponentにHeroDetailComponentを追加する
AppComponent
はmaster/detailを表示します。以前は自分自身でヒーローを表示していましたが、その部分のテンプレートは切り出しました。よって、今後はHeroDetailComponent
に表示を委任します。
そこで、AppComponent
テンプレートの下のほうに<hero-detail>
要素を追加します。<hero-detail>
はHeroDetailComponent
においてCSSセレクタとして指定したのを覚えていますでしょうか。
<hero-detail>
を記述する際、AppComponent
のselectedHero
属性をHeroDetailComponent
のhero
属性にバインドしたいので、次のように記述します:
<hero-detail [hero]="selectedHero"></hero-detail>
これにより、selectedHero
が変化するとHeroDetailComponent
は表示すべき新たなヒーローを入手します。
AppComponent
は次のようになっているはずです:
import { Component } from '@angular/core'; import { Hero } from './hero'; const HeRoEs: Hero[] = [ { id: 11, name: 'ビアンカ' }, { id: 12, name: 'フローラ' }, { id: 13, name: 'デボラ' } ]; @Component({ selector: 'app-root', template: `<h1>{{title}}</h1> <h2>私のヒーロー一覧</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" (click)="onSelect(hero)" [class.selected]="hero === selectedHero"> <span class="badge">{{hero.id}}</span>{{hero.name}} </li> </ul> <hero-detail [hero]="selectedHero"></hero-detail> `, styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Tour of Heroes'; selectedHero: Hero; heroes = HeRoEs; // heroes: ではない点に注意 onSelect(myhero: Hero): void { this.selectedHero = myhero; } }
ブラウザを開くと、以前と同じようにヒーローを選択するとそのヒーローの詳細が画面下部に表示されます。しかし、以前と異なり、詳細はHeroDetailComponent
が表示しています。
AppComponent
を2つのコンポーネントにリファクタリングすることにより、次のメリットがあります:
AppComponent
の責任を減らすことによりシンプルにしたAppComponent
を触ることなくHeroDetailComponent
を通じてエディタ部分を改良できる- ヒーローの詳細表示を触ること無く
AppComponent
を進化させることができる HeroDetailComponent
のテンプレートを将来作るかもしれない親コンポーネントで再利用できる
最後に、本章終了時点のソースを記載します:
hero-detail.component.ts
import { Component, Input } from '@angular/core'; import { Hero } from './hero'; @Component({ selector: 'hero-detail', template: ` <div *ngIf="hero"> <div> <input [(ngModel)]="hero.name"> </div> <div><label>id: </label>{{hero.id}}</div> <div><label>name: </label>{{hero.name}}</div> </div> ` }) export class HeroDetailComponent { @Input() hero: Hero; }
app.component.ts
import { Component } from '@angular/core'; import { Hero } from './hero'; const HeRoEs: Hero[] = [ { id: 11, name: 'ビアンカ' }, { id: 12, name: 'フローラ' }, { id: 13, name: 'デボラ' } ]; @Component({ selector: 'app-root', template: `<h1>{{title}}</h1> <h2>私のヒーロー一覧</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" (click)="onSelect(hero)" [class.selected]="hero === selectedHero"> <span class="badge">{{hero.id}}</span>{{hero.name}} </li> </ul> <hero-detail [hero]="selectedHero"></hero-detail> `, styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Tour of Heroes'; selectedHero: Hero; heroes = HeRoEs; // heroes: ではない点に注意 onSelect(myhero: Hero): void { this.selectedHero = myhero; } }
hero.ts
export class Hero { id: number; name: string; }
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule} from '@angular/forms'; import { AppComponent } from './app.component'; import { HeroDetailComponent } from './hero-detail.component'; @NgModule({ declarations: [ AppComponent, HeroDetailComponent ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }