[和訳] 4. Multiple Components (Angular公式チュートリアル)

この投稿は、Angular公式チュートリアルの [4. Multiple Components](https://v4.angular.io/tutorial/toh-pt3) を和訳した記事です。
職場で行うAngular勉強会を円滑に進めることを目的としています。

筆者自身もAngularについては初級者で、英語の知識も乏しいため、記事内容への指摘がありましたら是非コメントをお願いします。

**なお、この記事はAngular v4に対応したチュートリアルの和訳記事です。**
**最新版のチュートリアルではありませんのでご注意ください。**

【チュートリアル一覧】
_1. Introduction_ : [和訳ページ](https://qiita.com/Imamotty/items/75f2974d95acc15359fd) / [原文](https://v4.angular.io/tutorial)
_2. The Hero Editor_ : [和訳ページ](https://qiita.com/Imamotty/items/5b9290fb674b6feda58c) / [原文](https://v4.angular.io/tutorial/toh-pt1)
_3. Master/Detail_ : [和訳ページ](https://qiita.com/Imamotty/items/eac7cdef533ab7cb4152) / [原文](https://v4.angular.io/tutorial/toh-pt2)
**_4. Multiple Components_ : このページです / [原文](https://v4.angular.io/tutorial/toh-pt3)**
_5. Services_ : 和訳ページ作成中 / [原文](https://v4.angular.io/tutorial/toh-pt4)
_6. Routing_ : 和訳ページ作成中 / [原文](https://v4.angular.io/tutorial/toh-pt5)
_7. HTTP_ : 和訳ページ作成中 / [原文](https://v4.angular.io/tutorial/toh-pt6)

***
# Multiple Components
`AppComponent`は現時点ですべて処理を実行しています。
最初は、単一のヒーローの詳細を示しました。
その後、ヒーローのリストとヒーローの詳細の両方を持つ_Master/Detail_フォームになりました。
まもなく新しい要件と機能が生まれます。
しかし、1つのコンポーネントに機能を積み重ね続けることはできません。
なぜならメンテナンス性が低いからです。

特定の責務や機能に焦点を絞ったサブコンポーネントに分割する必要があります。
最終的に、`AppComponent`は、それらのサブコンポーネントを司るシンプルな骨格になる可能性があります。

このページでは、そういった方針へ向けた最初のステップとして、ヒーローの詳細ビューを再利用可能な別のコンポーネントに分割します。
このページが完成したら、アプリはこの例のようになります。
https://v4.angular.io/generated/live-examples/toh-pt3/eplnkr.html

## 前回どこで終わったか
このページを開始する前に、`Tour of Heroes`の初期の構造と同じ構造であることを確認してください。 そうでない場合は、前のページに戻ります。

<img width="694" alt="スクリーンショット 2017-10-30 12.43.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/26577/dc1a40cc-b4c3-8fa1-00cf-ec667784ac56.png">

[これまでと同じように](https://v4.angular.io/tutorial/toh-pt1#keep-transpiling)ターミナルウ画面に`npm start`コマンドを入力して、
`Tour of Heroes`を構築している間にアプリをコンパイル・実行し続けるようにしておいてください。

## `HeroDetailComponent`(ヒーロー詳細コンポーネント)を作る
`app/`フォルダーに`hero-detail.component.ts`という名前のファイルを追加します。
このファイルには、新しい`HeroDetailComponent`というコンポーネントを持たせます。

ファイル名とコンポーネント名は、[Angularスタイルガイド](https://v4.angular.io/guide/styleguide#naming)に記載されている標準に従います。

* Componentクラス名は、アッパーキャメルケースで書かれ、 "Component"という単語で終わるべきです。 ヒーロー詳細コンポーネントクラス名は`HeroDetailComponent`です。
* コンポーネントファイルの名前は、ダッシュ記号で綴る必要があります。各単語はダッシュで区切り、`.component.ts`で終わります。 `HeroDetailComponent`クラスは`hero-detail.component.ts`ファイルに格納されます。

次のように`HeroDetailComponent`を書き始めてみましょう。

```TypeScript:app/hero-detail.component.ts(initial_version)
import { Component } from '@angular/core';

@Component({
  selector: 'hero-detail',
})
export class HeroDetailComponent {
}
```

コンポーネントを定義するには、常に`Component`シンボルをimportします。

`@Component`デコレータは、コンポーネントのAngluarメタデータを提供します。
`hero-detail`というCSSセレクタ名は、親コンポーネントのテンプレート内のこのコンポーネントを識別する要素タグと一致します。
このチュートリアルの最後で、`<hero-detail>`要素を`AppComponent`テンプレートに追加します。

コンポーネントを他の場所でもインポートできるようにするため、常にコンポーネントクラスをエクスポートしてください。

### ヒーロー詳細テンプレート
ヒーロー詳細ビューを`HeroDetailComponent`に移動するには、ヒーロー詳細コンテンツを`AppComponent`テンプレートの下部から切り取り、`@Component`メタデータの新しい`template`プロパティに貼り付けます。

`HeroDetailComponent`は、「選択されたヒーロー」ではなく、「(1人の)ヒーロー」の情報を持ちます。 
テンプレート内のすべての`selectedHero`という単語を`hero`という単語に置き換えます。
完了したら、新しいテンプレートは次のようになります。

```src/app/hero-detail.component.ts(template)
@Component({
  selector: 'hero-detail',
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  `
})
```

### `hero`プロパティを追加する
`HeroDetailComponent`のテンプレートは、コンポーネントの`hero`プロパティにバインドされます。
`hero`プロパティを`HeroDetailComponent`クラスに追加します。

```TypeScript:src/app/hero-detail.component.ts(hero_property)
hero: Hero;
```

`hero`プロパティは`Hero`クラスのインスタンスとして型付けされます。
`Hero`クラスはまだ`app.component.ts`ファイルにあります。
しかし今、`Hero`クラスを参照する必要がコンポーネントが2つになりました。
[Angularのスタイルガイド](https://v4.angular.io/guide/styleguide#rule-of-one)では、ファイルごとに1つのクラスを推奨しています。

`Hero`クラスを`app.component.ts`から独自の`hero.ts`ファイルに移動しましょう。

```TypeScript:src/app/hero.ts
export class Hero {
  id: number;
  name: string;
}
```

`Hero`クラスが独自のファイルになったので、`AppComponent`と`HeroDetailComponent`はそれをインポートする必要があります。
`app.component.ts`ファイルと`hero-detail.component.ts`ファイルの上部に次のimport文を追加します。

```TypeScript:src/app/hero-detail.component.ts
import { Hero } from './hero';
```

### `hero`プロパティは「入力」プロパティ

このページの後半では、親である`AppComponent`が、選択されたヒーローを`HeroDetailComponent`の`hero`プロパティにバインドすることによって、
表示するヒーローを子の`HeroDetailComponent`に伝えます。
バインディングする方法は次のようになります。

```html
<hero-detail [hero]="selectedHero"></hero-detail>
```

`hero`プロパティの周りを角括弧で囲んで、等号（=）の左側に置くと、プロパティバインディング式の対象になります。
対象のバインディングプロパティ(今回の場合は`hero`)は子の`HeroDetailComponent`で「入力」プロパティとして宣言する必要があります。
それ以外の場合、Angularはバインディングを拒否し、エラーをスローします。

最初に、`Input`シンボルをimportするために`@angular/core`のimport文を修正します。

```TypeScript:src/app/hero-detail.component.ts(抜粋)
import { Component, Input } from '@angular/core';
```

その後、importした`@Input`デコレータを`hero`の宣言前に置くことで、`hero`が「入力」プロパティであることを宣言します。

```TypeScript:src/app/hero-detail.component.ts(抜粋)
@Input() hero: Hero;
```

> 「入力」プロパティの詳細については、[属性ディレクティブ](https://v4.angular.io/guide/attribute-directives#why-input)のページを参照してください。

これで終わりです。
`hero`プロパティは、`HeroDetailComponent`クラスが持つ唯一のプロパティです。

```TypeScript:src/src/app/hero-detail.component.ts
export class HeroDetailComponent {
  @Input() hero: Hero;
}
```

`HeroDetailComponent`クラスが行うのは、「入力」プロパティである`hero`プロパティで`Hero`オブジェクトを受け取った後、自身のテンプレートの`hero`プロパティにバインドすることだけです。

`HeroDetailComponent`の全体像は以下のようになります。

```src/app/hero-detail.component.ts
import { Component, Input } from '@angular/core';

import { Hero } from './hero';
@Component({
  selector: 'hero-detail',
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  `
})
export class HeroDetailComponent {
  @Input() hero: Hero;
}
```

## `AppModule`で`HeroDetailComponent`を宣言する
すべてのコンポーネントは、`NgModule`で(`NgModule`でのみ)宣言する必要があります。

エディタで`app.module.ts`を開き、`HeroDetailComponent`を参照できるようにインポートします。

```TypeScript:src/app/app.module.ts
import { HeroDetailComponent } from './hero-detail.component';
```

`HeroDetailComponent`をモジュールの`declarations`配列に追加します。

```TypeScript:src/app/app.module.ts
declarations: [
  AppComponent,
  HeroDetailComponent
],
```

一般に、`declarations`配列には、モジュールに属するアプリケーションコンポーネント、パイプ、およびディレクティブのリストが含まれています。
あるコンポーネントが他のコンポーネントを参照するには、そのモジュール内でコンポーネントを宣言する必要があります。
このモジュールは、`AppComponent`と`HeroDetailComponent`の2つのアプリケーションコンポーネントのみを宣言しています。

> NgModulesの詳細については、[NgModulesガイド](https://v4.angular.io/guide/ngmodule)を参照してください。

## `AppComponent`に`HeroDetailComponent`を追加する
`AppComponent`はまだ*master/detail*ビューのままです。
テンプレートのヒーロー詳細部分を切り取る前なので、ヒーロー詳細はまだ`AppComponent`自身が表示しています。
今度はヒーロー詳細部分を`HeroDetailComponent`に委譲します。

`hero-detail`は`HeroDetailComponent`メタデータのCSSセレクタであることを思い出してください。
これは、`HeroDetailComponent`を表す要素のタグ名です。

ヒーロー詳細ビューを使用していた`AppComponent`テンプレートの下部に`<hero-detail>`要素を追加します。

`AppComponent`の`selectedHero`プロパティを`HeroDetailComponent`の`hero`プロパティにバインドして、
親である`AppComponent`を`HeroDetailComponent`と調整します。

```TypeScript:app.component.ts(抜粋)
<hero-detail [hero]="selectedHero"></hero-detail>
```

`selectedHero`が変更されるたびに、`HeroDetailComponent`は新しいヒーローを取得して表示します。

修正された`AppComponent`のテンプレートは、次のようになります。

```app.component.ts(抜粋)
template: `
  <h1>{{title}}</h1>
  <h2>My Heroes</h2>
  <ul class="heroes">
    <li *ngFor="let hero of heroes"
      [class.selected]="hero === selectedHero"
      (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </li>
  </ul>
  <hero-detail [hero]="selectedHero"></hero-detail>
`,
```

## 何が変わったか？
以前同様、ユーザーがヒーロー名をクリックするたびに、ヒーローの詳細がヒーローリストの下に表示されます。
しかし今は、`HeroDetailView`がその詳細を提示しています。

元の`AppComponent`を2つのコンポーネントに分けるリファクタリングは、現在、そして将来に渡って利益をもたらします。

1. `AppComponent`の責任を軽減することによって`AppComponent`が単純化されました。
2. 親である`AppComponent`に手を入れることなく、`HeroDetailComponent`を豊かなヒーローエディタに進化させることができます。
3. ヒーローの詳細ビューに触れることなく、`AppComponent`を進化させることができます。
4. 将来の親コンポーネントのテンプレートで`HeroDetailComponent`を再利用することができます。

### アプリの構造を確認する
次のファイル構成になっていることを確認しましょう。
<img width="695" alt="スクリーンショット 2017-11-08 6.02.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/26577/203569c0-90dd-fbe1-8bc8-88443b10d19f.png">

このページで解説したコードは次のとおりです。

```TypeScript:src/app/hero-detail.component.ts
import { Component, Input } from '@angular/core';

import { Hero } from './hero';
@Component({
  selector: 'hero-detail',
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  `
})
export class HeroDetailComponent {
  @Input() hero: Hero;
}
```

```TypeScript:src/app/app.component.ts
import { Component } from '@angular/core';

import { Hero } from './hero';

const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <hero-detail [hero]="selectedHero"></hero-detail>
  `,
  styles: [`
    .selected {
      background-color: #CFD8DC !important;
      color: white;
    }
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
      width: 15em;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
    }
    .heroes li.selected:hover {
      background-color: #BBD8DC !important;
      color: white;
    }
    .heroes li:hover {
      color: #607D8B;
      background-color: #DDD;
      left: .1em;
    }
    .heroes .text {
      position: relative;
      top: -3px;
    }
    .heroes .badge {
      display: inline-block;
      font-size: small;
      color: white;
      padding: 0.8em 0.7em 0 0.7em;
      background-color: #607D8B;
      line-height: 1em;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }
  `]
})
export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}
```

```TypeScript:src/app/hero.ts
export class Hero {
  id: number;
  name: string;
}
```

```TypeScript:src/app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';

import { AppComponent }        from './app.component';
import { HeroDetailComponent } from './hero-detail.component';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule
  ],
  declarations: [
    AppComponent,
    HeroDetailComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }
```

## まとめ
このページであなたが達成したことは次のとおりです：

* 再利用可能なコンポーネントを作成しました。
* コンポーネントが入力を受け入れる方法を学びました。
* 必要なアプリケーションディレクティブを`NgModule`で宣言することを学びました。`@NgModule`デコレータの`declarations`配列にディレクティブを列挙しました。
* 親コンポーネントを子コンポーネントにバインドする方法を学びました。

あなたのアプリはこの例のように見えるはずです。
https://v4.angular.io/generated/live-examples/toh-pt3/eplnkr.html

## 次のステップ
`Tour of Heroes`アプリは共有コンポーネントでより再利用しやすくなりましたが、
そのモックデータはまだ`AppComponent`内でハードコードされています。
この状態は持続可能ではありません。
データアクセスは別のサービスにリファクタリングし、データを必要とするコンポーネント間で共有する必要があります。

次のチュートリアルページ[(原文)](https://v4.angular.io/tutorial/toh-pt4)でサービスを作成する方法を学びます。