Angluar2のクイックスタートとチュートリアルを実施 - その5

前回の投稿[Angluar2のクイックスタートとチュートリアルを実施 - その4](http://qiita.com/gambare/items/bd14e8f36cc40194c5dd)の続きです。
前回作成したソースを使用します。

### 本投稿の参照元(英語)

(バージョンが異なりますが、大きな影響はないと思います。)
[Services - ts](https://angular.io/docs/ts/latest/tutorial/toh-pt4.html)

なお、本章記載中にAngular2の正式最終リリースがされちゃいました。
http://angularjs.blogspot.jp/2016/09/angular2-final.html


### 本章で学ぶこと

以下を学習します。

 - 複数のコンポーネントで共有可能なサービスクラスを作成する。
 - `ngOnInit`ライフサイクルフック
 - `AppComponent`のプロバイダとして`HeroService`を定義する
 - プロミスの作り方と使い方


### 実行結果

本章を完了すると以下リンク先のようなものが出来上がります。
見た目は前回,前々回のアプリと変わっていません。
[live-examples](https://angular.io/resources/live-examples/toh-4/ts/plnkr.html)

### 事前準備

以下のコマンドによりサーバの起動を行います。プログラムの変更が即座にブラウザに反映されます。(サーバを落としていなければ不要です。)

```bash
cd angular2-tour-of-heroes
npm start
```


# ヒーローサービスの作成

`app/hero.service.ts`を作成します。
`'@angular/core'`から`Injectable`をインポートし、`HeroService`クラスを定義します。

```ts:app/hero.service.ts

import { Injectable } from '@angular/core';

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

## Injectableサービス

`Injectable`のインポートにより、`@Injectable()`デコレータが使用可能になります。
※`()`は必ず必要ですので忘れない様にしてください。
TypeScriptは`@Injectable()`を見、AngularがDIできるメタデータを発行します。
まだここでは`HeroService`は依存対象を何も持っていません。
`@Injectable()`デコレータを使用することは一貫性と将来性のためのベストプラクティスです。

## ヒーロー一覧の取得
`getHeroes`メソッドをスタブとして追加します。実装は後で行います。

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

このアプリを使用するユーザは、何のサービスからデータが取得されているかを知りません。ウェブサービスからかもしれないし、ローカルストレージからかもしれないし、モックからかもしれません。
また、コンポーネントからデータアクセス部分を取り除くことは良い手法です。ヒーロー情報に触れるためにコンポーネントをいじる必要がないためです。

## ヒーローのモックデータ

`app/app.component.ts`にあるヒーローのモックデータを別ファイル`app/mock-heroes.ts`を作成し移します。ここにいるべきクラスではないためです。

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

`app.component.ts`の`HEROES`配列を削除した代わりに、`AppComponent`クラスに、`heroes: Hero[]`を定義してあげます。

```ts:app.component.tsのAppComponentクラス
heroes: Hero[];
```

## ヒーローのモックデータに戻る

`HeroService`で、モックの`HEROES`をインポートし、同クラスの`getHeroes`メソッドで`HEROES`を返す様にします。サーバからデータを取得してくるイメージです。
`app/hero.service.ts`は以下の様になります。

```ts: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`を使用する準備が整いました。まずは`HeroServiceを`インポートします。

```ts:
import { HeroService } from './hero.service';
```

尚、`HeroService`は、以下の様にnewするべきではありません。

```ts:
heroService = new HeroService(); // don't do this
```

その理由は以下のとおりです。

 - コンポーネントは`HeroService`の作られ方を知る必要がある。コンストラクタを修正しようとした場合に使用している全箇所で修正が必要になるためテストがめんどうになる
 - 何のサービスがキャッシュしているかわからず、また共有できない
 - `AppComponent`を特定のサービス`HeroService`にしてしまうと、変更に弱くなる

などなど

## `HeroService`のインジェクト(注入)

`HeroService`を使用するため、インジェクトする側(`AppComponent`)に以下の2箇所を変更します。

1. コンストラクタを追加する。(プライベートなプロパティが定義される)
2. `Component`デコレータに`provides`を追加する

以下のように`AppComponent`クラスにコンストラクタ[^constructor]を追加します。

```ts:app/app.component.ts
constructor(private heroService: HeroService) { }
```
[^constructor]: TypeScriptのコンストラクタは、クラス内に`constructor()`で宣言します

コンストラクタ自体は何もしませんが、引数がprivateな`heroService`プロパティとして定義され、`AppComponent`がインジェクトするクラスの対象となります。(コンストラクタ内で`this.heroService = heroService`が暗黙で定義されます)
これにより、`AppComponent`クラスが作成されたときに、`HeroService`のインスタンスが提供されます。
インジェクトする側は、`HeroService`の使い方を知らないため、現時点では以下のエラーで失敗します。

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

インジェクトする側は`HeroService`を<b>プロバイダー</b>(サービス提供者)として登録
する必要があります。`app/app.component.ts`の`@Component`デコレータに`providers`配列と、その要素に`HeroService`を追加します。

```ts:app/app.component.tsの@Componentデコレータ
@Component({
  selector: 'my-app',
  template: ~省略~,
  styles: ~省略~,
  providers: [HeroService]
})
```
この`providers`は、`AppComponent`がつくられる時に`HeroService`のインスタンスを生成する様に指示します。`AppComponent`は`HeroService`サービスを使用し、ヒーロー一覧を取得したり、サービスのチャイルドコンポーネントを使ったりできます。

 - DI(Dependency Injection)については、@hshimoさんの[猿でも分かる! Dependency Injection: 依存性の注入](http://qiita.com/hshimo/items/1136087e1c6e5c5b0d9f)にあります。
 - Angular2のDIについては、@laco0416さんの[Angular2のDIを知る](http://qiita.com/laco0416/items/61eed550d1f6070b36ab)に詳しく書いてあります。
 - Angular2公式のDIの説明は[こちら](https://angular.io/docs/ts/latest/guide/dependency-injection.html)(英語)

## `AppComponent`の`getHeroes`メソッド

`heroService`を使いましょう。`AppComponent`クラスに`getHeroes()`メソッドを作成します。

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

この`getHeroes()`メソッドにより、サービスからデータを取得できますが、このメソッドは、どこで呼び出すべきでしょうか。

## ngOnInitライフサイクルフック

`getHeroes()`メソッドはコンストラクタではなく、<b>ngOnInit</b><I>ライフサイクルフック</I>[^hook]にて呼び出します。経験上コンストラクタにはできるだけロジック(特にサーバアクセス)を書かない方がいいためです。
Angularでは重要な場面(作成時、変更時、デストラクト時)で実行する様々なインタフェース(ライフサイクルフック)を持っています。これらのインタフェースはシングルメソッドであり、実装することにより呼び出されます。

[^hook]: イメージ的にはgitやsubversionなどにあるフック処理みたいなものでしょうか

ライフサイクルフックについての詳細は[こちら](https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html)(英語)
`ngOnInit`以外のI/Fについても記載があります。

`ngOnInit`は以下の様に使用します。データがバインドされ、コンストラクタが実行された後、`ngOnInit()`は呼ばれます。

```ts:app/app.component.ts

import { OnInit } from '@angular/core';

export class AppComponent implements OnInit {
  ngOnInit(): void {
    this.getHeroes();
  }
}
```

これで期待通り動作しますが、もうちょっと修正します。

# 非同期サービスとプロミス

`HeroService`はモックデータの取得なので、即時に値を取得できますが、リモートサーバなどで即答を期待できない場合<I>プロミス</I>を使います。

プロミスについては@koki_cheeseさんの[今更だけどPromise入門](http://qiita.com/koki_cheese/items/c559da338a3d307c9d88)が勉強になります。

## プロミスで`HeroService`を作成する

プロミスは、処理対象の準備ができたら呼び出し元へコールバックします。例えば非同期サーバーに問い合わせを行い、返事がもらえたらその結果を適切に処理します。
`HeroService`の`getHero`の戻り値を`viod`から`Promise`へ、また、`Promise`を返す様にします。[^promise]

[^promise]: ここでの戻り値`Promise<Hero[]>`はJavaやC++のジェネリクスやテンプレートみたいなものだと思います

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

## プロミスの挙動

現在`AppComponent`クラスの`getHeroes`メソッドは以下です。

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

`HeroService`の変更により、`getHeroes()`メソッドからの戻り値`Promise<Hero[]>`を正しく`this.heroes`プロパティの型`Hero[]`に変更するため、以下の様に変更します。

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

ここの`=>`は、アロー関数を使用しています。Javascriptのアロー関数については、@you21979@githubさんの[アロー関数の個人的なハマりどころまとめ](http://qiita.com/you21979@github/items/1f521e4f23e1b989827d)が参考になります。
ここでは、`AppComponent`クラスのプロパティ`heroes: Hero[]`に`HeroService`クラスからの戻り値`Promose<Hero[]>`の`Hero[]`を代入しています。
尚、TypeScriptではクラスメンバを表すthisは必須です。

これで、プロミスの実装ができました。

## 補足: 遅延
今のモックは即答しますが、サーバとの接続が遅い場合のシミュレーションを行います。
`HeroService`クラスに`Hero`クラスをインポートし、`getHeroesSlowly`メソッドを作成します。

```ts:app/hero.service.ts
getHeroesSlowly(): Promise<Hero[]> {
  return new Promise<Hero[]>(resolve =>
    setTimeout(resolve, 2000)) // delay 2 seconds
    .then(() => this.getHeroes());
}
```

`getHeroes()`の様に動作しますが、2秒間ディレイします。
`AppComponent`クラスの`heroService.getHeroes`を`heroService.getHeroesSlowly`に置換することで挙動を確認できます。

-------
本章は以上です。[次章](http://qiita.com/gambare/items/3585c57860e9f15120ff)ではルーティングを説明します。