2. リストの表示　Angular チュートリアル

# 参考

https://angular.jp/tutorial/toh-pt2

# 前段

このページでは「 Tour of Heroes 」アプリケーションを拡張してヒーローのリストを表示し、ユーザーがヒーローを選択してヒーローの詳細を表示できるようにする。

# ヒーローのモックを作成する

まずは、表示するためのいくつかのヒーローが必要だろう。

最終的には、リモートのデータサーバーからそれらのヒーローを取得する。
ひとまず、サーバーからデータが返ってきたと仮定して *ヒーローのモック* を作成しよう。

`mock-heroes.ts` と呼ばれるファイルを `src/app/` フォルダに作成しよう。
`HEROES` 定数を 10 人のヒーローの配列として定義し、エクスポートしよう。
ファイルは次のようになるだろう。

```typescript:src/app/mock-heroes.ts
import { Hero } from './hero';

export const HEROES: Hero[] = [
  { id: 11, name: 'Dr 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' }
];
```

`HeroesComponent` クラスのファイルを開いて `HEROES` モックをインポートしよう。

```typescript:src/app/heroes/heroes.component.ts
import { HEROES } from '../mock-heroes';
```

同じファイル (`HeroesComponent` クラス) で、 `heroes` という名前のコンポーネントプロパティを定義して、バインディングのために `HEROES` 配列を公開しよう。

```typescript:src/app/heroes/heroes.component.ts
export class HeroesComponent implements OnInit {

  heroes = HEROES;
}
```

# *[ngFor](https://angular.jp/api/common/NgForOf) でヒーローを一覧表示する

`HeroesComponent` テンプレートを開き、次のように変更しよう。

* `<h2>` を先頭に追加しよう
* その下に HTML の純不動リスト (`<ul>') を追加しよう
* `<li>` を `hero` プロパティを表示する `<ul>` の内側に挿入しよう
* スタイルを設定するためにいくつかの CSS のクラスを振る (CSS のスタイルは間もなく追加する)

このようになる。

```html:heroes.component.html
<h2>My Heroes</h2>
<ul class="heroes">
  <li>
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
```

これは一人のヒーローを示している。
それらをすべてリストするには、ヒーローのリストを反復処理するために、 `*ngFor` を `<li>` に追加する。

　```html
<li *ngFor="let hero of heroes">
```

プロパティ 'hero' が存在しないため、エラーが表示される。
個々のヒーローにアクセスしてすべてのヒーローをリストアップするには、 `<li>` に `*ngFor` を追加してヒーローのリストを繰り返し表示する。

この例の構文は次の通り。

* `<li>` はホスト要素
* `heroes` はモックのヒーローリストを保持する `HeroesComponent` クラスのリスト
* `hero` は各ループ毎のリストに、現在のヒーローオブジェクトを保持する

> `ngFor` の前のアスタリスク (*) を忘れないように。
> これは構文において重要な部分。

## ヒーローを修飾する

ユーザーがカーソルを置いてリストからヒーローを選択するとき、ヒーローのリストは魅力的で視覚的に目立たせる必要がある。
[最初のチュートリアル](https://angular.jp/tutorial/toh-pt0#app-wide-styles) では、アプリケーション全体の基本的なスタイルを `styles.css` に設定した。
このスタイルシートにはヒーローのリストのためのスタイルは含めていなかった。

`styles.css` にさらにスタイルを追加し、コンポーネントを追加するときにそのスタイルシートを拡大し続けることができる。

もしかしたら、特定のコンポーネントのためにプライベートなスタイルを定義し、コンポーネントが必要とするすべて - コードや、 HTML 、 CSS - を 1 箇所にまとめて管理することを好むかもしれない。

このアプローチは他の場所でコンポーネントを再利用することを容易にし、グローバルに適用されたスタイルが異なる場合であってもコンポーネントが意図した外観を提供する。

プライベートなスタイルは `@Component.styles` 配列内にインラインで定義するか、スタイルシートファイルとして特定の `@Component.styleUrls` 配列の中で識別されるスタイルシートファイルとして定義する。

CLI が `HeroesComponent` を生成するとき、 `HeroesComponent` のために空の `heroes.component.css` が作成され、 `@COmponent.styleUrls` はこのように指し示される。

```typescript:src/app/heroes/heroes.component.ts
@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
```

`heroes.component.css` を開いて、 `HeroesComponent` のためのプライペートなスタイルを貼り付ける。
これらは、このガイドの末尾なる [最終的なコードレビュー](https://angular.jp/tutorial/toh-pt2#final-code-review) から見つけることができる。

> `@Component` のメタデータで識別されたスタイルとスタイルシートは、特定のコンポーネントにスコープされる。
> `heroes.component.css` のスタイルは `HeroesComponent` にのみ適用され、他の HTML や他のどのコンポーネント内の HTML にも影響しない。

# 詳細の表示

ユーザーがリストの中のヒーローをクリックしたとき、コンポーネントは選択されたヒーローの詳細をページの下部に表示させる必要がある。

この章では、ヒーローのアイテムがクリックされるのを待ち、クリックされたらヒーローの詳細を更新してみよう。

## クリックイベントのバインディングを追加する

クリックイベントのバインディングを `<li>` にこのように追加しよう。

```html:heroes.component.html
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
```

これは Angular の [イベントバインディング](https://angular.jp/guide/event-binding) シンタックスにおける 1 つの例。

`click` を囲っている括弧は、 Angular に `<li>` 要素の `click` イベントであることを伝える。
ユーザーが `<li>` をクリックすると、 Angular は `onSelect(hero)` 式を実行する。

次のセクションでは、 `HeroesComponent` で `onSelect()` メソッドを定義して、 `*ngFor` 式で定義されたヒーローを表示する。

## クリックイベントのハンドラーを追加する

コンポーネントの `hero` プロパティを `selectedHero` にリネームするが、まだ割り当てない。
アプリケーション起動時に *選択されたヒーロー* はない。

次のようにして `onSelect()` メソッドを追加し、クリックされたヒーローをテンプレートからコンポーネントの `selectedHero` に割り当てよう。

```typescript:src/app/heroes/heroes.component.ts
selectedHero?: Hero;
onSelect(hero: Hero): void {
  this.selectedHero = hero;
}
```

## 詳細セクションを追加する

現在、コンポーネントテンプレートにはリストがある。
リストのヒーローをクリックして、そのヒーローの詳細を表示するには、それをテンプレートでレンダリングするための詳細セクションが必要。
`heroes.component.html`　のリストセクションの下に以下を追加する。

```html:heroes.component.html
<h2>{{selectedHero.name | uppercase}} Details</h2>
<div><span>id: </span>{{selectedHero.id}}</div>
<div>
  <label for="hero-name">Hero name: </label>
  <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
</div>
```

ブラウザを更新すると、アプリケーションは壊れてしまう。

ブラウザの開発者ツールを開いて、コンソールの中のこのようなエラーメッセージ を探そう。

```:Terminal
Error: src/app/heroes/heroes.component.html:7:20 - error TS2532: Object is possibly 'undefined'.

7 <h2>{{selectedHero.name | uppercase}} Details</h2>
```

## 何が起きたのか？

アプリケーションを起動した際、 `selectedHero` は *意図的に* `undefined` 。

`selectedHero` のプロパティを参照するテンプレート内での式のバインディングは - `{{selectedHero.name}}` のような式 - 選択されたヒーローが存在しないため *失敗* しなければならない。

## 修正しよう - **ngIf* を使って空の details を非表示にする

コンポーネントは `selectedHero` が存在する場合のみ、選択されたヒーローの詳細を表示する必要がある。

ヒーローの詳細を HTML の `<div>` で囲おう。
Angular の `*ngIf` ディレクティブを `<div>` に追加し、 `selectedHero` に設定しよう。

> [ngif](https://angular.jp/api/common/NgIf) の前のアスタリスク (*) を忘れないように。
> これは構文において重要な部分。

```html:src/app/heroes/heroes.component.html
<div *ngIf="selectedHero">

  <h2>{{selectedHero.name | uppercase}} Details</h2>
  <div><span>id: </span>{{selectedHero.id}}</div>
  <div>
    <label for="hero-name">Hero name: </label>
    <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
  </div>

</div>
```

ブラウザを更新すると、名前の一覧が再度表示される。
詳細のエリアは空白になっている。
ヒーローのリストの中からヒーローをクリックし、詳細を表示しよう。
アプリケーションは再び動き出す。
ヒーローたちはリストの中に表示され、クリックされたヒーローの詳細はページの下部に表示される。

## なぜこれが動くのか

`selectedHero` が定義されていないとき、 `ngIf` は DOM からヒーローの詳細を削除する。
心配する `selectedHero` へのバインディングは存在しない。

ユーザーがヒーローを選択すると `selectedHero` は値を持ち `ngIf` はヒーローの詳細を DOM の中に挿入する。

## 選択されたヒーローを装飾する

選択されたヒーローを識別しやすくするために、 [先に追加したスタイル](https://angular.jp/tutorial/toh-pt2#styles) の中の `.selected` という CSS クラスをクラスを使用することができる。
ユーザーがクリックしたときに `.selected` クラスを `<li>` に適用するには、クラスバインディングを使用する。

Angular の [クラスバインディング](https://angular.jp/guide/attribute-binding#class-binding) は条件に応じた CSS クラスの追加と削除ができる。
装飾したい要素に `[class.some-css-class]="some-condition"` を追加するだけ。

`HeroesComponent` テンプレートの中の `<li>` に `[class.selected]` バインディングを追加しよう。

```html:heroes.component.html
[class.selected]="hero === selectedHero"
```

現在の行のヒーローが `selectedHero` と同じ場合、 Angular は `selected` の CSS クラスを追加する。
2 つのヒーローが異なる場合には、 Angular はそのクラスを削除する。

完成した `<li>` はこのようになる。

```haml:heroes.component.html
<li *ngFor="let hero of heroes"
  [class.selected]="hero === selectedHero"
  (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
```

# 最終的なコードレビュー

こちらが `HeroesComponent` のスタイルを含んだ、このページで解説したコードファイル。

```typescript:src/app/mock-heroes.ts
import { Hero } from './hero';

export const HEROES: Hero[] = [
  { id: 11, name: 'Dr 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' }
];
```

```typescript:src/app/heroes/heroes.component.ts
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})

export class HeroesComponent implements OnInit {

  heroes = HEROES;
  selectedHero?: Hero;

  constructor() { }

  ngOnInit() {
  }

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

```html:src/app/heroes/heroes.component.html
<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>

<div *ngIf="selectedHero">

  <h2>{{selectedHero.name | uppercase}} Details</h2>
  <div><span>id: </span>{{selectedHero.id}}</div>
  <div>
    <label for="hero-name">Hero name: </label>
    <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
  </div>

</div>
```

```css:src/app/heroes/heroes.component.css
/* HeroesComponent's private CSS styles */
.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:hover {
  color: #2c3a41;
  background-color: #e6e6e6;
  left: .1em;
}
.heroes li.selected {
  background-color: black;
  color: white;
}
.heroes li.selected:hover {
  background-color: #505050;
  color: white;
}
.heroes li.selected:active {
  background-color: black;
  color: white;
}
.heroes .badge {
  display: inline-block;
  font-size: small;
  color: white;
  padding: 0.8em 0.7em 0 0.7em;
  background-color:#405061;
  line-height: 1em;
  position: relative;
  left: -1px;
  top: -4px;
  height: 1.8em;
  margin-right: .8em;
  border-radius: 4px 0 0 4px;
}

input {
  padding: .5rem;
}
```

# まとめ

* 「 Tour of Heroes 」アプリケーションはヒーローのリストと詳細ビューを表示する
* ユーザーはヒーローを選択し、そのヒーローの詳細を見ることができる
* リストを表示するために `*ngFor` を使った
* HTML のブロックを条件付きで含める、または除外するために `*ngIf` を使った
* CSS スタイルの class を `クラス` バインディングで切り替えることができる