[和訳] 5. Services (Angular公式チュートリアル)

この投稿は、Angular公式チュートリアルの [5. Services](https://v4.angular.io/tutorial/toh-pt4) を和訳した記事です。
職場で行う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://qiita.com/Imamotty/items/796338e1e3224eb9ba34) / [原文](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)

***
# Services
Tour of Heroesアプリが進化するにつれて、ヒーロのーデータにアクセスする必要のあるコンポーネントが更に追加されるでしょう。

同じコードを繰り返しコピーして貼り付ける代わりに、再利用可能な単一のデータサービスを作成し、それを必要とするコンポーネントに挿入します。 
別のサービスを使用することで、コンポーネントの傾きをなくし、ビューをサポートすることに重点を置いて、モックサービスでコンポーネントを単体テストすることが容易になります。

データサービスは常に非同期であるため、Promiseベースのデータサービスでページを完成させます。

このページが完成したら、アプリはこの例のようになります。
https://v4.angular.io/generated/live-examples/toh-pt4/eplnkr.html

## 前回どこで終わったか
Tour of Heroesの続きに進む前に、以下の構造を持っていることを確認してください。 そうでない場合は、前のページに戻ります。
<img width="612" alt="service_1.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/26577/33ba2f55-7d4e-8567-af34-f1f4c78d7339.png">

## ヒーローサービスを作成する

### `HeroService`を作成する
`app`フォルダに`hero.service.ts`という名前のファイルを作成します。

>サービスファイルの命名規則は、小文字のサービス名の後に`.service`を付けたものです。
>複数語のサービス名の場合は、[ダッシュケース](https://v4.angular.io/guide/glossary#dash-case)を小さくしてください。
>たとえば、`SpecialSuperHeroService`のファイル名は`special-super-hero.service.ts`です。

`HeroService`クラスに名前を付け、他のファイルからimportできるようにexportします。

```TypeScript:src/app/hero.service.ts(starting_point)
import { Injectable } from '@angular/core';

@Injectable()
export class HeroService {
}
```

### 注入可能なサービス
Angularの`Injectable`関数をインポートし、その関数を`@Injectable()`デコレータとして適用したことに注目してください。

>かっこを忘れないでください。それらを省略すると、調査が困難なエラーが発生します。

`@Injectable()`デコレータは、サービスに関するメタデータを出力するようにTypeScriptに指示します。
メタデータは、Angularが他の依存関係をこのサービスに注入する必要があることを指定します。

`HeroService`は現時点では依存関係はありませんが、最初から`@Injectable()`デコレータを適用すると、一貫性と将来性が保証されます。

## ヒーローデータを取得する

`getHeroes()`メソッドのスタブを追加してください。

```TypeScript:src/app/hero.service.ts(getHeroes_stub)
@Injectable()
export class HeroService {
  getHeroes(): void {} // stub
}
```

`HeroService`は、Webサービス、ローカルストレージ、またはモックのデータソースのどこからでも`Hero`データを取得できます。
コンポーネントからのデータアクセスを削除するということは、ヒーローデータを必要とするコンポーネントに触れることなく、いつでも実装に関する考えを変えることができるということを意味します。

### モックのヒーローデータを移動する
`app.component.ts`から`HEROES`配列を切り取り、`mock-heroes.ts`という名前の`app`フォルダにある新しいファイルに貼り付けます。
さらに、ヒーロー配列が`Hero`クラスを使用するため、`import {Hero} ...`ステートメントをコピーします。

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

export 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' }
];
```

`HEROES`定数は、`HeroService`など他の場所でもimportできるようにexportされます。

`app.component.ts`では、`HEROES`配列を切り取った箇所で、初期化されていない`heroes`プロパティを追加します。

```TypeScript:src/app/app.component.ts(heroes_property)
heroes: Hero[];
```

### モックのヒーローデータを返す
`HeroService`に戻り、モックの`HEROES`をインポートし、`getHeroes()`メソッドからそれを返します。
`HeroService`は次のようになります。

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

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
  getHeroes(): Hero[] {
    return HEROES;
  }
}
```

### ヒーローサービスをインポートする
`HeroService`を他のコンポーネントで使用する準備が整ったので、まずは`AppComponent`から始めていきます。
`HeroService`をインポートして、コード内で参照できるようにします。

```TypeScript:src/app/app.component.ts(hero-service-import)
import { HeroService } from './hero.service';
```

### `HeroService`では`new`を使用しない
`AppComponent`は、実行時の具体的な`HeroService`インスタンスをどのように取得すればよいのでしょうか？

あなたは次のように`HeroService`の新しいインスタンスを新規作成することができるでしょう。

```TypeScript:src/app/app.component.ts
heroService = new HeroService(); // don't do this
```

ただし、この方法は理想的ではありません。理由は次のとおりです。

* コンポーネントは`HeroService`を作成する方法を知っていなければなりません。`HeroService`コンストラクターを変更する場合は、サービスを作成したすべての場所を見つけて更新する必要があります。 複数の場所でコードを修正すると、エラーが発生しやすくなり、テストの負担が増します。
* `new`を使用するたびにサービスを作成します。 サービスがヒーローのキャッシュを行い、そのキャッシュを他のコンポーネントと共有する場合、どうなるでしょうか？ 実現できないでしょう。
* `AppComponent`が`HeroService`の特定の実装にロックされているため、オフラインでの操作やテスト用に異なるバージョンのモックの使用など、さまざまなシナリオの実装を切り替えるのは難しいでしょう。

### `HeroService`を注入する
`new`を使用する代わりに、次の2行を追加します。

* プライベートプロパティも定義するコンストラクタを追加する
* コンポーネントの`provider`のメタデータに`HeroService`を追加する

コンストラクタの追加:

```TypeScript:src/app/app.component.ts(constructor)
constructor(private heroService: HeroService) { }
```

コンストラクタ自体は何もしません。
このパラメータは、プライベートの`heroService`プロパティを定義すると同時に、そのプロパティを`HeroService`の注入が行われる場所として識別します。

そしてAngularは、`AppComponent`を作成するときに`HeroService`のインスタンスを提供することを知っています。

>依存性注入の詳細については、[Dependency Injection](https://v4.angular.io/guide/dependency-injection)ページを参照してください。

しかし_Injector_は、HeroServiceの作成方法をまだ分かりません。 
今すぐコードを実行した場合、Angularはこのエラーで失敗します。

```
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
```

Injectorに`HeroService`の作成方法を教えるには、`@Components`で呼び出されたコンポーネントメタデータの一番下に次の`providers`配列プロパティを追加します。

```TypeScript:src/app/app.component.ts(providers)
providers: [HeroService]
```

`providers`配列は、`AppComponent`を作成するときに`HeroService`の新しいインスタンスを作成するようにAngularに指示します。
`AppComponent`とその子コンポーネントは、そのサービスを使用してヒーローデータを取得できます。

### `AppComponent`の`getHeroes()`
サービスはプライベートの`heroService`変数にあります。
そのサービスを呼び出して、データを1行にまとめることができます。

```TypeScript:src/app/app.component.ts
this.heroes = this.heroService.getHeroes();
```

あなたは1つの行をラップするために特別なメソッドを必要としません。 とにかくこれを書いてください。

```TypeScript:src/app/app.component.ts(getHeroes)
getHeroes(): void {
  this.heroes = this.heroService.getHeroes();
}
```

### _ngOnInit_ライフサイクルフック
`AppComponent`は問題なくヒーローデータを取得して表示する必要があります。

コンストラクタで`getHeroes()`メソッドを呼び出すことができますが、コンストラクタには複雑なロジック、特にデータアクセスメソッドなどのサーバーを呼び出すロジックが含まれてはいけません。
コンストラクタは、コンストラクタパラメータをプロパティへ紐づけるような単純な初期化のためのものです。

Angularに`getHeroes()`を呼ばせるために、Angularの_ngOnInitライフサイクルフック_を実装できます。
Angularは、コンポーネントライフサイクルに関する重要な瞬間（作成時、変更後、および最終的な破棄時）にアクセスするためのinterfaceを提供しています。

各interfaceには1つのメソッドがあります。
コンポーネントがそのメソッドを実装すると、しかるべきタイミングでAngularがそのメソッドを呼び出します。

>ライフサイクルフックの詳細については、[Lifecycle Hooks](https://v4.angular.io/guide/lifecycle-hooks)ページを参照してください。

`OnInit`インターフェイスの基本的な概要は次のとおりです。（これをコードにコピーしないでください）

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

export class AppComponent implements OnInit {
  ngOnInit(): void {
  }
}
```

`OnInit`インターフェイスの実装をエクスポートステートメントに追加します。

```
export class AppComponent implements OnInit {}
```

初期化ロジックを内部に持つ`ngOnInit`メソッドを記述します。
Angularは適切なタイミングでこのメソッドｗｐ呼び出します。
この場合、`getHeroes()`を呼び出して初期化します。

```TypeScript:src/app/app.component.ts(ng-on-init)
ngOnInit(): void {
  this.getHeroes();
}
```

ヒーロー名をクリックすると、ヒーローのリストとヒーローの詳細ビューが表示され、期待どおりに実行されるはずです。

## 非同期なサービスとPromise
`HeroService`はモックヒーローのリストを直ちに返します。その`getHeroes()`シグネチャは同期しています。

```TypeScript:src/app/app.component.ts
this.heroes = this.heroService.getHeroes();
```

最終的に、主人公のデータはリモートサーバーから取得されます。
リモートサーバーを使用する場合、ユーザーはサーバーが応答するのを待つ必要はありません。
そのため、待機中にUIをブロックすることはできません。

ビューをレスポンスと調整するには、_Promises_を使用します
これは、`getHeroes()`メソッドのシグネチャを変更する非同期技術です。

### ヒーローサービスでPromiseを作成する
_Promise_は、基本的には、結果の準備ができたらコールバックすることを約束します。
非同期サービスに何らかの作業を行い、コールバック関数を与えるように依頼します。
サービスはその作業を行い、最終的には結果またはエラーで関数を呼び出します。

このPromiseを返す`getHeroes()`メソッドで`HeroService`を更新します。

```TypeScript:src/app/hero.service.ts(抜粋)
getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES);
}
```

あなたはまだモックデータを使っています。
即座に解決されたPromiseをモックヒーローと結びつけて返すことで、超高速でゼロ遅延のサーバーの動作をシミュレートしています。

### Promiseの振る舞い
`HeroService`への変更の結果、`this.heroes`はヒーローの配列ではなくPromiseが設定されます。

```TypeScript:src/app/app.component.ts(getHeroes(修正前))
getHeroes(): void {
  this.heroes = this.heroService.getHeroes();
}
```

Promiseが解決されたら、それを実行するように実装を変更する必要があります。
Promiseが正常に解決すると、あなたはヒーローを表示します。

Promiseの`then()`メソッドへの引数としてコールバック関数を渡します。

```TypeScript:src/app/app.component.ts(getHeroes(修正後))
getHeroes(): void {
  this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}
```

コールバックは、コンポーネントの`heroes`プロパティをサービスが返すヒーローの配列に設定します。
アプリはまだ実行中で、ヒーローのリストを表示し、詳細ビューで名前選択に応答します。