Angular公式ドキュメントに沿ってAngular理解③

# はじめに

[Angular公式ドキュメントに沿ってAngular理解②](https://qiita.com/honda28/items/0d786987bf5a70e0294d)でルーティングの設定方法を理解した。引き続き、公式ドキュメントに沿って理解していく。
また本記事からローカル環境での実装を行っているため、一部 Angular CLI コマンドを使っている。ローカル環境の構築に関しては、[Angular公式チュートリアルのローカル環境セットアップ](https://qiita.com/honda28/items/18eee802657886ac6ef9)を参照。

# Angular チュートリアル

[データの管理](https://angular.jp/start/start-data)にしたがって、サンプルコードを元に修正・追加をしながら進めていく形式になっている。本記事では以下を行う。

- カートに入れた商品リストを管理するカートサービスを追加
- 商品の詳細ビューに Buy ボタンを追加
- カート内の商品を表示するカートコンポーネントを追加
- ~~HttpClient を使用して .json ファイルから配送料金を取得する shipping コンポーネントを追加~~

## ショッピングカートサービスの作成

サービスとは Angular の依存性の注入（以降 DI; Dependency Injection) を使用して、アプリの任意の部分で使用できるクラスのインスタンスを指す。
このチュートリアルでは、ユーザが商品をカートに追加し、カート内の商品の情報を保存するカートサービスを作成する。

### 1. サービスの生成

まず Angular CLI コマンドで新しいサービスを作成する。サービス名は `cart` とした。

```bash
$ ng g s cart # ng generate service cart でも可
```

実行後、`src/app/cart.service.ts` と `src/app/cart.service.spec.ts` が作成される。（`src/app/cart.service.spec.ts` は今回使用しない。）

```typescript:src/app/cart.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class CartService {

  constructor() { }
}
```

デフォルトで記入されている `@Injectable({providedIn: 'root'})` の意味がわからなかったため調査。[モジュールに依存オブジェクトを提供する](https://angular.jp/guide/providers)によると、[Injectable](https://angular.io/api/core/Injectable) のオプションである `providedIn` では、サービスを提供する範囲を指定できるらしい。正直イメージが湧かず意味も理解しきれていないので、ここでは一旦置いておく。

### 2. プロパティの定義

`Product` インターフェースをインポートして、商品の配列をカートに格納する `items` プロパティを定義する。

```typescript:src/app/cart.service.ts
import { Injectable } from '@angular/core';
import { Product } from './products';

@Injectable({
  providedIn: 'root'
})
export class CartService {
  items: Product[] = [];

  constructor() { }
}
```

### 3. メソッドの定義

カートへの商品の追加をする `addToCart` メソッド、商品リストの取得をする `getItems` メソッド、商品リストのクリアを行う `clearCart` メソッドを定義する。

```typescript:src/app/cart.service.ts
import { Injectable } from '@angular/core';
import { Product } from './products';

@Injectable({
  providedIn: 'root'
})
export class CartService {
  items: Product[] = [];

  constructor() { }

  addToCart(product: Product) {
    this.items.push(product);
  }

  getItems() {
    return this.items;
  }

  clearCart() {
    this.items = [];
    return this.items;
  }
}
```

## カートサービスの使用

上記で作成したカートサービスを使用して、商品をカートに追加する。

### 1. カートサービスのインポート

`product-details.component.ts` にて、カートサービスをインポートする。

```typescript:product-details.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { Product, products } from '../products';
import { CartService } from '../cart.service';

...
```

### 2. カートサービスの注入

`product-details.component.ts` にて、カートサービスを constructor に追加して注入し、注入されたサービスを用いた `addToCart` メソッドを定義する。 

```typescript:product-details.component.ts
...
export class ProductDetailsComponent implements OnInit {
  product: Product | undefined;

  constructor(
    private route: ActivatedRoute,
    private cartService: CartService
  ) {}

  ngOnInit() {
    // First get the product id from the current route.
    const routeParams = this.route.snapshot.paramMap;
    const productIdFromRoute = Number(routeParams.get('productId'));

    // Find the product that correspond with the id provided in route.
    this.product = products.find(product => product.id === productIdFromRoute);
  }

  addToCart(product: Product) {
    this.cartService.addToCart(product);
    window.alert('Your product has been added to the cart!');
  }
}
```

### 3. イベントバインディング

上記で定義したメソッドに Buy ボタンの `click()` イベントをバインドする。

```html:product-details.component.html
<h2>Product Details</h2>

<div *ngIf="product">
  <h3>{{ product.name }}</h3>
  <h4>{{ product.price | currency }}</h4>
  <p>{{ product.description }}</p>

  <button (click)="addToCart(product)">Buy</button>
</div>
```

商品詳細ページで Buy ボタンが表示されていて、クリックするとメッセージが表示されることが確認できる。

## カートビューの作成

カートに追加した商品を見るビューを追加する。

### 1. コンポーネントの生成

まず Angular CLI コマンドで新しいコンポーネントを作成する。サービス名は `cart` とした。

```bash
$ ng g c cart # ng generate component cart でも可
```

実行後、`src/app/cart` ディレクトリが作成される。

### 2. ルーティングの追加

作成したカートコンポーネントを扱うために、`app.module.ts` にてパスを追加する。（`declarations` にも追加が必要と思うが、自動で追加されていた。）

```typescript:app.module.ts
...
@NgModule({
  imports: [
    BrowserModule,
    ReactiveFormsModule,
    RouterModule.forRoot([
      { path: '', component: ProductListComponent },
      { path: 'products/:productId', component: ProductDetailsComponent },
      { path: 'cart', component: CartComponent }
    ])
  ],
  declarations: [
    AppComponent,
    TopBarComponent,
    ProductListComponent,
    ProductAlertsComponent,
    ProductDetailsComponent,
    CartComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
```

### 3. `routerLink` を追加

Checkout ボタンから上記で追加した `/cart` の URL にルーティングされるように `routerLink` ディレクティブを追加する。

```top-bar.component.html
<a [routerLink]="['/']">
  <h1>My Store</h1>
</a>

<a routerLink="/cart" class="button fancy-button">
  <i class="material-icons">shopping_cart</i>
  Checkout
</a>
```

Checkout ボタンを押すと、ページ遷移することが確認できる。
ここで My Store ボタンでの `routerLink` ディレクティブの書き方（`[routerLink]="['/']"`）はプロパティパインディング `[ ]` を使用しているため変数を含む（動的に変化する）URL では役に立ちそうだが、今の場合冗長なだけに見える。Checkout ボタンと同様に `routerLink="/"` とシンプルに書き換えておく。

### 4. サービスの利用

先ほどはカートに商品を追加するために `product-details.component.ts` でカートサービスを利用した。ここでは新たに作成したカートコンポーネントでカートに追加されている商品のリストを表示したい。そこで `cart.component.ts` でカートサービスを利用し、商品リストを取得する。

```typescript:cart.component.ts
export class CartComponent {
  items = this.cartService.getItems();

  constructor(
    private cartService: CartService
  ) { }
}
```

上記により、カートに入っている商品のリストが `items` プロパティに格納される。最後に `cart.component.html` にて商品の名前と価格を表示するようコードを書く。

```html:cart.component.html
<h3>Cart</h3>

<div class="cart-item" *ngFor="let item of items">
  <span>{{ item.name }}</span>
  <span>{{ item.price | currency }}</span>
</div>
```

各種実行が確認できる。

# おわりに

[データの管理](https://angular.jp/start/start-data)では `HttpClient` の使用などがまだあるが、一旦ここで切ることとした。Angular の基本となるコンポーネント、テンプレート、サービスや依存性の注入などについては理解したため、以降アプリケーションを実際に作る中で、わからない部分が出てくれば逐次調査して進められればと思う。