Angular入門 未経験から1ヶ月でサービス作れるようにする その4. モデルとループと分岐と

[前回](https://qiita.com/seteen/items/732bacf604ab8d18a84c)は、複数ページを作ってページ遷移を試しながら、ルーティングの基礎を学びました。

本記事では、

- Angularでのデータ管理の基礎
  - モデルクラスの作成
- Angularのディレクティブ基礎
  - HTMLでの分岐
  - HTMLでのループ

をやっていきます。

## この記事のソースコード

[https://github.com/seteen/AngularGuides/tree/入門その04](https://github.com/seteen/AngularGuides/tree/%E5%85%A5%E9%96%80%E3%81%9D%E3%81%AE04)

## Angularでのデータ管理の基礎

前回、 `product-list.component` , `product-detail.component` を作成しました。
前回までは特に名前に意味がなかったのですが、今回から意味を加えたいと思います。

| コンポーネント名 | 役割 |
|:--|:--|
| product-list.component | 商品リストページ。商品の一覧を表示する画面 |
| product-detail.component | 商品詳細ページ。商品一覧でクリックされた商品の詳細を表示する画面 |

これから各ページの内容を作成していきますが、これらのページを作るときに重要なのは、 `商品(product)` とはどのようなものなのかを各コンポーネントが共通認識として理解できるようにすることです。

Angularプロジェクト内での共通認識を行うために、モデルのファイルを作成します。
プロジェクト内で共通で使うファイルなので、

```
src/app/shared/models
```

に保存します。

> TIPS: ファイルの配置
> Angularとして、各ファイルをどこに置かないといけない、という制約はありません。
> プロジェクトに関わっているエンジニア内でここが良い、と思う場所に置くのが良いと思います。
> Angular公式のガイドラインもあるので参考にしてください。
> https://angular.io/guide/styleguide#overall-structural-guidelines

### product.ts モデルの作成

商品(product) には、 `一意に識別できるID(id)` , `名前(name)` , `価格(price)` , `説明(description)` の要素をもたせます。

``` src/app/shared/models/product.ts
export class Product {
  id: number;
  name: string;
  price: number;
  description: string;

  constructor(id, name, price, description) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.description = description;
  }
}
```

このように、共通で使えるファイルに `商品(product)` モデルを定義することで、このプロジェクト内での商品の共通認識が作れました。
`Typescript` なのでJavascriptしか知らない方には書き方に違和感があると思います。 `Typescript` の書き方などは、次回解説していきます。
今回は、なんとなくこういうものなんだと思っておいてください。

### コンポーネントからモデルを利用する

Productモデルを作成したので、 `product-detail.component.ts` で実際に表示してみましょう。

``` src/app/product/product-detail/product-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { Product } from '../../shared/models/product';

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html',
  styleUrls: ['./product-detail.component.scss'],
})
export class ProductDetailComponent implements OnInit {
  product: Product;

  constructor() { }

  ngOnInit() {
    this.product = new Product(1, 'Angular入門書「天地創造の章」', 3800, '神は云った。「Angularあれ」。するとAngularが出来た。');
  }

}
```

``` src/app/product/product-detail/product-detail.component.html
<div class="container">
  <div class="title">商品詳細</div>
  <div class="product-detail-container">
    <div class="param-line">
      <div class="label">ID</div>
      <div class="value">{{ product.id }}</div>
    </div>
    <div class="param-line">
      <div class="label">名前</div>
      <div class="value">{{ product.name }}</div>
    </div>
    <div class="param-line">
      <div class="label">価格</div>
      <div class="value">{{ product.price }}</div>
    </div>
    <div class="param-line">
      <div class="label">説明</div>
      <div class="value">{{ product.description }}</div>
    </div>
  </div>
  <div class="footer">
    <div class="button black" routerLink="/products">商品一覧へ</div>
  </div>
</div>
```

``` src/app/product/product-detail/product-detail.component.scss
.container {
  margin: auto;
  padding: 32px 0;
  width: 800px;
  .title {
    padding: 8px 0;
    text-align: center;
    width: 100%;
    font-weight: 600;
    font-size: 18px;
  }

  .product-detail-container {
    padding-left: 48px;
    border: 1px solid #D9DBDE;
    border-radius: 4px;
    background-color: #FFFFFF;

    .param-line {
      display: flex;
      padding: 16px 8px;

      &:nth-child(n+2) {
        border-top: 1px solid #D9DBDE;
      }

      .label {
        width: 100px;
      }
    }
  }

  .footer {
    display: flex;
    justify-content: ce;
    margin-top: 20px;
  }
}
```

上記を修正し、 `ng serve` で起動し、 `http://localhost:4200/products/test` にアクセスします。

<img width="941" alt="001.png" src="https://qiita-image-store.s3.amazonaws.com/0/67424/29f33186-8600-fe7b-5a7e-9553257c6a5b.png">

このようにProductの情報が画面に表示されているはずです。

#### 解説

HTML, CSS が少し複雑になりましたが、基本的には、 入門その2 で `app.component.ts` を変更したときと同様のことをしています。

##### product-detail.component.ts

Angular CLI のコンポーネントの作成で、作成されたファイルに、最初から `ngOnInit()` というメソッドがありました。
まだ解説していませんでしたので、解説します。

Angularのコンポーネントには、ライフサイクルのイベントフックが用意されています。
つまり、コンポーネントができてから、なくなるまでのイベントがいくつか用意されていて、 Typescript のメソッド名が決まっているということです。

例えば、 **コンポーネントができたときに実行したい処理** , **コンポーネントが描画されたら実行したい処理** などの処理を各メソッドが決まっているので、
用途にあったメソッドをうまく使って表示の調整をしたりできます。

その中でも最も利用するのが、 `ngOnInit()` です。 `ng` というのは A `ng` ular の **ng** を取っているものなので、 `ng` + `OnInit` つまり、初期化後に呼び出されるメソッドになります。

`constructor` と何が違うんだ、と思うかもしれませんが、今のところはそのあたりは気にせず、 `ngOnInit()` に書いておいた方が予想外の動きをせずに安全なので初期化処理はこちらに書いておきましょう。
（最初の学習ではとりあえずやってみる、が大切です。やってるうちにだいたいわかってきます）

> TIPS: ライフサイクルのフック
> 他にもどのような種類のフックがあるのかは、Angularの公式ページを参照してみてください。
> https://angular.io/guide/lifecycle-hooks

##### product-detail.component.html

Productの各要素を `{{}}` を使って表示しています。 `{{}}` の中で使えるのは、関連している Typescript のコンポーネントのクラスの `public` なインスタンス変数とメソッドです。
HTMLで使いたい値があれば、インスタンス変数かメソッドにしておきましょう（ `WebStorm` があれば補完で出てくるので何が使えるかがすぐわかります )。

#### product-detail.component.scss

見た目を最低限良い感じにするための最小限のコードです。

コンポーネント間でCSSがわかれるので、自由にクラス名を定義して書いてます。

Angularの入門なので、特に内容には触れませんが、 私は `display: flex` がとても好きです。

### 実際のコミット

https://github.com/seteen/AngularGuides/compare/59ac529e6b8bbaa51cc1ebbaebe9627221b62f31...a10ab9ab08deee2bd95043c1c299a60763e29312

## Angularのディレクティブ基礎( *ngIf によるHTML内での分岐 )

今度は 商品リストページ ( `product-list.component` ) に移ります。

商品リストを作っていく前に、商品を取得している間の表示を作成します。（APIで商品情報を取得するまでの表示）

``` src/app/product/product-list/product-list.component.html
<div class="container">
  <div class="title">商品一覧</div>
  <div class="product-list-container">
    <div class="waiting-for-products">
      商品を取得しています...
    </div>
  </div>
</div>
```

``` src/app/product/product-list/product-list.component.scss
.container {
  margin: auto;
  padding: 32px 0;
  width: 800px;
  .title {
    padding: 8px 0;
    text-align: center;
    width: 100%;
    font-weight: 600;
    font-size: 18px;
  }
}
```

HTMLとSCSSしか変更していません。
現状の状態で、画面を見てみましょう（ `ng serve` -> `http://localhost:4200/products` にアクセス)

<img width="791" alt="002.png" src="https://qiita-image-store.s3.amazonaws.com/0/67424/cb2ccb6c-d8c1-7318-3294-ef307c748cdc.png">


### データを取得したら別の表示になるようにする

次に、APIアクセスが終わり商品情報が取得できたと仮定して（今はまだAPIアクセスはしません）、商品情報が取得できる前と後とで表示を変更してみましょう。


``` src/app/product/product-list/product-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Product } from '../../shared/models/product';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
  products: Product[] = null;

  constructor() { }

  ngOnInit() {
    setTimeout(() => {
      this.products = [
        new Product(1, 'Angular入門書「天地創造の章」', 3800, '神は云った。「Angularあれ」。するとAngularが出来た。'),
        new Product(2, 'Angularを覚えたら、年収も上がって、女の子にももてて、人生が変わりました！', 410, '年収300万のSEが、Angularと出会う。それは、小さな会社の社畜が始めた、最初の抵抗だった。'),
        new Product(3, '異世界転生から始めるAngular生活(1)', 680,
          'スパゲッティの沼でデスマーチ真っ最中の田中。過酷な日々からの現実逃避か彼は、異世界に放り出され、そこでAngularの入門書を拾う。現実逃避でさえ、プログラミングをするしかない彼に待ち受けるのは！？'),
      ];
    }, 3000);
  }

}
```

``` src/app/product/product-list/product-list.component.html
<div class="container">
  <div class="title">商品一覧</div>
  <div class="product-list-container">
    <div class="waiting-for-products" *ngIf="products === null; else productList">
      商品を取得しています...
    </div>
    <ng-template #productList>
      <div class="product-list-table">
        <div class="product-line header">
          <div class="product-id">ID</div>
          <div class="product-name">名前</div>
          <div class="product-price">価格</div>
        </div>
        <div class="product-line">
          <div class="product-id">{{ products[0].id }} </div>
          <div class="product-name">{{ products[0].name }}</div>
          <div class="product-price">{{ products[0].price }}</div>
        </div>
        <div class="product-line">
          <div class="product-id">{{ products[1].id }} </div>
          <div class="product-name">{{ products[1].name }}</div>
          <div class="product-price">{{ products[1].price }}</div>
        </div>
        <div class="product-line">
          <div class="product-id">{{ products[2].id }} </div>
          <div class="product-name">{{ products[2].name }}</div>
          <div class="product-price">{{ products[2].price }}</div>
        </div>
      </div>
    </ng-template>
  </div>
</div>
```

``` src/app/product/product-list/product-list.component.scss
.container {
  margin: auto;
  padding: 32px 0;
  width: 800px;
  .title {
    padding: 8px 0;
    text-align: center;
    width: 100%;
    font-weight: 600;
    font-size: 18px;
  }

  .product-list-container {
    .product-list-table {
      border: 1px solid #D9DBDE;
      border-radius: 4px;
      overflow: hidden;

      .product-line {
        display: flex;
        padding: 16px 24px;

        &.header {
          background-color: #F5F5F5;
        }

        &:nth-child(n+2) {
          border-top: 1px solid #D9DBDE;
        }

        .product-id {
          width: 40px;
        }
        .product-name {
          width: 630px;
        }
      }
    }
  }
}
```

上記のように変更したら、画面を見てみましょう（ `ng serve` -> `http://localhost:4200/products` にアクセス)

![003.gif](https://qiita-image-store.s3.amazonaws.com/0/67424/53f02a74-e01c-ecb9-47df-9bcd3034cd36.gif)

#### 解説
##### product-list.component.ts
`ngOnInit()` で、 `setTimeout` を使って、 3秒後に `products` にデータが入るようにしています。

##### product-list.component.html

`商品取得中` , `商品取得後` の表示を分けるために、 `*ngIf` というディレクティブを利用しています。

``` .html
    <div class="waiting-for-products" *ngIf="products === null; else productList">
```

中身は条件式になっていて、 `products` が `null` であればこの `div` を表示する。それ以外の場合は、 `productList` で示された要素を持っているところを表示する、という意味になります。
`product-list.component.ts` で コンポーネント初期化の3秒後に `products` が `null` でなくなるので、最初の3秒間だけ表示されています。

``` .html
    <ng-template #productList>
```

`*ngIf` の `else productList` の部分で指定しているのは上記部分になります。
この `else` の部分は、 `ng-template` という要素を使ってください。 また、その中に `else` で示された `productList` を `#` 付きで指定します。
することで、3秒後にこの要素が表示されるようになります。

> TIPS: ディレクティブとは
> ディレクティブというのは、HTMLにおける属性（attribute)のようなもので、
> コンポーネントに対して特定の作用を付与できるモジュールのようなものと思うと良いかと思います。
> 私は、 [`src` と `srcset` を書くのが面倒なので一つ書くだけで両方書いてくれるディレクティブ](https://qiita.com/seteen/items/31bfd256c1667ed72c2e) とか作ったりしてます

#### 実際のコミット

https://github.com/seteen/AngularGuides/compare/a10ab9ab08deee2bd95043c1c299a60763e29312...b33ece2437c3c57c9d6bcd0ede4b5c8149df3668

## Angularのディレクティブ基礎( *ngFor によるHTML内でのループ )

上記例では、3つ存在する商品全てを HTML の中でそれぞれ記載しました。
ただ、このやり方では、商品の数が決まっていないとダメですし、なにより面倒です。

Angularには、 HTMLタグを繰り返し（ループ）記述したいためのディレクティブ `*ngFor` があります。

これを使ってコードを書き換えてみます。

``` src/app/product/product-list/product-list.component.html
<div class="container">
  <div class="title">商品一覧</div>
  <div class="product-list-container">
    <div class="waiting-for-products" *ngIf="products === null; else productList">
      商品を取得しています...
    </div>
    <ng-template #productList>
      <div class="product-list-table">
        <div class="product-line header">
          <div class="product-id">ID</div>
          <div class="product-name">名前</div>
          <div class="product-price">価格</div>
        </div>
        <div class="product-line" *ngFor="let product of products">
          <div class="product-id">{{ product.id }} </div>
          <div class="product-name">{{ product.name }}</div>
          <div class="product-price">{{ product.price }}</div>
        </div>
      </div>
    </ng-template>
  </div>
</div>
```

`*ngFor` の中身は、 `let xxx of yyy` と記載するのが基本です。これは、 `yyy` の 要素一つ一つを `xxx` という変数に入れてループする。というものです。
通常の `Javascript` にも存在している `for` 文の書き方とほとんど同じなのでわかりやすいと思います。

> TIPS: ngFor の色々な変数
> ngForでは、ループでよく使うインデックス、最初か最後かなどの情報が簡単に取得できるようになっています。
>  `<li *ngFor="let product of products; let i = index; let f = first>`
> のように、すると、 `i` に `index` が、 `f` には最初の要素かどうかの boolean が入ります。便利ですね！
> 詳しくはこちら↓
> https://angular.io/api/common/NgForOf#local-variables

#### ここまでのコミット
https://github.com/seteen/AngularGuides/commit/243c16a7a4e6774ce236992ff9bee91e0691128f

## おまけ

`*ngIf` と同じようなもので、 `ngSwitch` というものがあります。

名前からわかるように、 `switch` 文が使えるものです。

使い方としては、

```
<div [ngSwitch]="state">
  <div *ngSwitchCase="1">One</div>
  <div *ngSwitchCase="2">Two</div>
  <div *ngSwitchDefault>Default</div>
</div>
```

のように使います。分岐が多いときは、こちらを使っても良いかもしれません。

## まとめ

今回は、プロジェクト内でデータのやり取りをするためのモデルの作り方と、 `*ngIf` による条件分岐 , `*ngFor` によるループを学びました。
少しずつ、Angularのことがわかり始めてきたのではないでしょうか。

次回は、Typescriptの基礎的な部分の解説と、Angularにおいてデータ管理部分をコンポーネントから切り離す方法を解説していきます。
[Angular入門 未経験から1ヶ月でサービス作れるようにする その5. サービスとTypescriptの基礎](https://qiita.com/seteen/items/6fcff31179fb9cf0e14d)

## 入門記事一覧
「Angular入門 未経験から1ヶ月でサービス作れるようにする」は、記事数が多いため、[まとめ記事](https://qiita.com/seteen/items/43908e33e08a39612a07) を作っています。
https://qiita.com/seteen/items/43908e33e08a39612a07