Angular入門 未経験から1ヶ月でサービス作れるようにする その5. サービスとTypescriptの基礎

[前回](https://qiita.com/seteen/items/445a550e27d9ff76eaec)は、

- Angularでのデータ管理の基礎（モデル作成）
- HTMLでの分岐(*ngIf)
- HTMLでのループ(*ngFor)

を学びました。

本記事では、

- ES6とTypescriptの基礎
  - Typescriptの型定義について
- Angularでのデータ管理の基礎2
  - サービスクラスの作成

をやっていきます。

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

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

## Typescriptの基礎

Angularでは、Typescriptを利用します。

今後の入門では、Typescriptのコードが増えていきますので、このあたりで基礎を学んでおきましょう。

### 型の指定

すでに何度か出てきましたが、 Typescript では、変数、メソッドそれぞれに型を定義します。

```.ts
export class Test {
  name: string; // <- これがインスタンス変数

  toString(input: string): string { // <- これがインスタンスメソッド
    return input;
  }
}
```

このように、 

- 変数定義は `変数名: 型`
- メソッド定義は `メソッド名(引数名: 引数名の型): メソッドの型 { メソッドの内容 }`

と書く必要があります。

### 型の種類
Typescriptで用意されている型として、よく使うのは、

- string : 文字列
- number : 数値
- boolean : 真偽値
- void: 何も返さないメソッドの型

です。また、自分で定義したクラスなどのクラスも型となります。
また、配列については、 `string[]` のように型の後ろに `[]` をつけることで表現します。

```.ts
names: string[]
```

また、 **なんでも良い** という型の `any` があります。

どの型を指定したら良いかわからないというときの最終手段です。

#### Interface
Typescript には、 Interface があります。
これは、Javaなどのように Class に継承するInterfaceとしても使えますが、
もう一つの使い方としてメソッドの引数などが複雑なときに利用することが多く、ObjectのKeyなどに制約をもたせることができます。

例を見ていきます。

```.ts
export interface TestInterface {
  name: string;
  price: number;
  description?: string;
}
```

という Interface があり、

```.ts
testMethod(input: TestInterface): void {
  ...
}
```

というTestInterfaceを引数に取るメソッドが合ったとします。

Interfaceを使うことによって、少し複雑な引数に対して制約をもたせることができ、上記のコードの意味は、 `name` と `price` は必須で、 `description` には `?` がついているので、これは `optional（合ってもなくても良い)` という意味になります。


そのため、例えばこのメソッドに対して、

```.ts
this.testMethod({name: 'test'});
```

のように実行しようとすると、 price が存在しないため、エラーが出ます。

![001.png](https://qiita-image-store.s3.amazonaws.com/0/67424/82d78178-1cc3-d2fb-f878-8294ca83692c.png)

また、下記のように存在しない要素を追加してもエラーが出ます。

```.ts
this.testMethod({name: 'test', price: 100, extend: 'feawfeaw'});
```

![002.png](https://qiita-image-store.s3.amazonaws.com/0/67424/5b570b02-a7e9-6e3d-b265-252fac7d428b.png)

#### Enum
また、意外とよく使うのが、 Enum です。
これは、ある変数が決まった値しか取らないときなどに使います。
例えばステータスとして、 `非公開( unpublished )` , `公開 ( published )`  があり、
それ以外の値をとらない変数があるような場合

``` .ts
export enum Status {
  unpublished = 'unpublished',
  published = 'published'
}
```

と記載します。
もし数値しか取らないで、 `非公開 (0)` , `公開 (1)` であったとすれば、下記のように記載できます。

```.ts
export enum Status {
  unpublished,
  published
}
```

つまり、何も指定しなければ、上から順に 0 から数値が割り当てられます。

これを使う側としては、

```.ts
export class TestClass {
  status: Status;
}
```

のように記載します。

これにより、 `status` には `Status` で定義した値しか入らない（静的に）ようになります。

#### Enum含めた型による制約の注意事項

`string` , `number` などの変数での型指定、 Enumによる値の制約、これらは、静的な解析時にのみ有効です。
そのため、動的に 別の型の値を入れられたとしても、その時点でエラーを起こしてくれたりはしません。（Javaなどとは違う)

これは、実行時には Javascript として実行されるため、Typescriptの恩恵は受けられないからです。例えばAPIで 本来は string を想定していたのに number が入っていたとして、
それを代入してもエラーになったりはしません。

型の制約は強力ですが、そのような場合もあるため、あまり頼りすぎて思考を停止しないように気をつけましょう。

> TIPS: Typescriptの便利な記法
> 例えば params という変数が定義されているとすると、
> `{ params }` と記載するだけで、 `{ params: params }` と解釈してくれます。
> 地味に便利です。

## private, protected, public

Typescript では、 `private` , `protected` , `public` という変数やメソッドのスコープを指定する修飾子が用意されています。

| 修飾子 | スコープ |
|:--|:--|
| private | 定義されたクラス内 |
| protected | 定義されたクラスとそれを継承しているクラス |
| public | 外部からアクセス可能 |

この修飾子は、

``` .ts
export class Test {
  private name: string;

  protected testMethod(): string {
    ...
  }
}
```

のように使います。何も修飾子を指定しなければ、 `public` として扱われます。

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

Angularでは、APIアクセスやコンポーネントをまたがるデータの管理などを、 `Service` クラスを使って行うことができます。

今回は、 `ProductService` クラスを作って `Product` のデータ管理をコンポーネントの外に出してみましょう。

### サービスクラスの作成

Angular CLI を用いてサービスを作成しましょう。

``` .shell
# ng g service shared/services/product
CREATE src/app/shared/services/product.service.spec.ts (380 bytes)
CREATE src/app/shared/services/product.service.ts (136 bytes)
```

今回は、 `src/app/shared/services/` ディレクトリにサービスを配置したかったので、上記のような記載にしました。

作成されたファイルを見てみましょう。 `spec.ts` ファイル（テストファイルです）もできていますが、今は無視します。

``` src/app/shared/services/product.service.ts
import { Injectable } from '@angular/core';

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

  constructor() { }
}
```

`@Injectable` というのは、サービスクラスに指定するおまじないです。
中に `{ providedIn: 'root' }` が入っていますが、これはルートレベルでこのサービスを利用するということになります。

サービスクラスは、指定した `providedIn` の単位でインスタンス化され(今回だとルートレベル。つまりプロジェクト全体)、一つのインスタンスが使い回されます。
いわゆるシングルトンに近い状態と思っていただけると良いかと思います。

これに `Product` のリストを取得するメソッドを追加していきます。

最終的には、ここでAPIアクセスを行うようになりますが、今はAPIアクセスしないようにします。

``` src/app/shared/services/product-service.ts
import { Injectable } from '@angular/core';
import { Product } from '../models/product';
import { Observable, of } from 'rxjs/index';

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

  constructor() { }

  list(): Observable<Product[]> {
    return of(this.products);
  }
}
```

`list()` メソッドを追加しました。型は、 `Observable<Product[]>` を指定しています。

現状では、API通信がないので同期処理になりますが、最終的にはAPI通信を行うので `list()` メソッドは非同期処理になります。

Angularでは、非同期処理には `RxJS` を利用します。 `Observable` というのは `RxJS` のクラスで、非同期処理におけるメインのクラスになります。
今の所は、 `Promise` のようなものだと思っていただければ良いかなと思います。


`of` というのも `RxJS` のメソッドで、これは与えられた内容で `Observable` を作るというものです。

つまり上記コードは、 `Observable` (観測可能) な `Product` の配列を返すメソッドになります。

#### `product-list.component.ts` の変更

次に、 `product-list.component.ts` を変更していきます。

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

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

  constructor(
    private productService: ProductService,
  ) {}

  ngOnInit() {
    this.productService.list().subscribe((products: Product[]) => {
      this.products = products;
    });
  }
}
```

元々は `ngOnInit` 内で `Product`　の配列を作成していましたが、 `ProductService` を利用するようになりました。

##### `constructor` の解説

`constructor` で `ProductService` の引数を呼んでいます。

Angularでは、この記述により `ProductService` をコンポーネントやサービスに挿入できます。 (いわゆる DI )

つまり、この記載で `ProductListComponent` に、 `productService` という変数が生成されたことになります。

##### ngOnInit の解説

`this.productService.list()` は、 `Obserbable<Product[]>` を返します。

先程記載した通り、これは `Observable` (観測可能) な `Product` の配列を返します。

`subscribe` は、 `Observable` のメソッドで対象の `Observable` の観測を開始するものです（APIアクセスの非同期処理を開始）。

観測が完了すると、 `subscribe` の引数のコールバックメソッドが呼ばれます。この引数には、 `Observable<Product[]>` の `Product[]` が入ることになります。

そのため、 `ProductService` の `list()` メソッドで返した配列がこのメソッドに入ります。

つまり、

``` .ts
this.productService.list().subscribe((products: Product[]) => {
```

の `products` に入っているということになります。

このメソッドの中で

``` .ts
this.products = products;
```

としてインスタンス変数に代入しています。
これにより、`product-list.component.ts` の `products` を表示するように記載しているViewに反映されます。

ただ、今回で、前回の 3秒待つ という記述を削除したので、少しだけ挙動が変わりました（すぐに商品一覧が表示される）。

### 動作確認

`ng serve` で起動して、 `http://localhost:4200/products` にアクセスして挙動を確認してみましょう。


<img width="825" alt="003.png" src="https://qiita-image-store.s3.amazonaws.com/0/67424/21690a76-bf1a-3d5e-09f3-694ae7d9ae07.png">

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

## product-detail.component.ts からの ProductServiceの利用

続いて、 `product-detail.component.ts` から `ProductService` を呼ぶように修正してみましょう。

### ProductServiceにidで商品が取得できるメソッドを追加する

`ProductService` に、 `get(id)` を追加します。

``` src/app/shared/services/product.service.ts
import { Injectable } from '@angular/core';
import { Product } from '../models/product';
import { Observable, of } from 'rxjs/index';

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

  constructor() { }

  list(): Observable<Product[]> {
    return of(this.products);
  }

  get(id: number): Observable<Product> {
    return of(this.products[id - 1]);
  }
}
```

`get(id)` メソッドは、idで指定されたProductの `Observable` を返すというものです。

### `product-detail.component.ts` からの利用

ほぼ `product-list.components.ts` と同様です。

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

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

  constructor(
    private productService: ProductService,
  ) {}

  ngOnInit() {
    this.productService.get(2).subscribe((product: Product) => {
      this.product = product;
    });
  }
}
```

- `constructor` にて ProductService の変数を得る
- `ngOnInit` で `get(id)` を `subscribe` (観測) して商品を取得しています
- あと、特に理由はありませんが、取得するものを `2` に変えました


### 動作確認
`ng serve` で起動して、 `http://localhost:4200/products/test` にアクセスしてみましょう。

<img width="858" alt="004.png" src="https://qiita-image-store.s3.amazonaws.com/0/67424/271f71ba-ede6-48cc-87a8-5c4215b80ac6.png">

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

## まとめ

今回は、Typescriptの基礎 と サービスクラスを使ったデータ管理の方法を学びました。

`Observable` という非同期処理の話が出てきました。
少し難しく感じたかもしれませんが、これも使っていくうちにわかっていきますので（私も最初はわけわからん、と思いつつ使っていて自然とわかるようになりました）、まずは使っていきましょう。

次回は、 Angularでのフォームの作り方や、クリックイベントの受け取り方などを解説していきます。

[Angular入門 未経験から1ヶ月でサービス作れるようにする その6. ngModelを使ったフォーム](https://qiita.com/seteen/items/4bc7dbd9b52ca20de63e)

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