10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-09-15

前回の投稿Angluar2のクイックスタートとチュートリアルを実施 - その4の続きです。
前回作成したソースを使用します。

本投稿の参照元(英語)

(バージョンが異なりますが、大きな影響はないと思います。)
Services - ts

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

本章で学ぶこと

以下を学習します。

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

実行結果

本章を完了すると以下リンク先のようなものが出来上がります。
見た目は前回,前々回のアプリと変わっていません。
live-examples

事前準備

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

cd angular2-tour-of-heroes
npm start

ヒーローサービスの作成

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

app/hero.service.ts

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

@Injectable()
export class HeroService {
}

Injectableサービス

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

ヒーロー一覧の取得

getHeroesメソッドをスタブとして追加します。実装は後で行います。

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

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

ヒーローのモックデータ

app/app.component.tsにあるヒーローのモックデータを別ファイルapp/mock-heroes.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.tsHEROES配列を削除した代わりに、AppComponentクラスに、heroes: Hero[]を定義してあげます。

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

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

HeroServiceで、モックのHEROESをインポートし、同クラスのgetHeroesメソッドでHEROESを返す様にします。サーバからデータを取得してくるイメージです。
app/hero.service.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をインポートします。

import { HeroService } from './hero.service';

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

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

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

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

などなど

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

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

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

以下のようにAppComponentクラスにコンストラクタ1を追加します。

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

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

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

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

app/app.component.tsの@Componentデコレータ
@Component({
  selector: 'my-app',
  template: ~省略~,
  styles: ~省略~,
  providers: [HeroService]
})

このprovidersは、AppComponentがつくられる時にHeroServiceのインスタンスを生成する様に指示します。AppComponentHeroServiceサービスを使用し、ヒーロー一覧を取得したり、サービスのチャイルドコンポーネントを使ったりできます。

AppComponentgetHeroesメソッド

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

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

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

ngOnInitライフサイクルフック

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

ライフサイクルフックについての詳細はこちら(英語)
ngOnInit以外のI/Fについても記載があります。

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

app/app.component.ts

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

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

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

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

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

プロミスについては@koki_cheeseさんの今更だけどPromise入門が勉強になります。

プロミスでHeroServiceを作成する

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

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

プロミスの挙動

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

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

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

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

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

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

補足: 遅延

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

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.getHeroesheroService.getHeroesSlowlyに置換することで挙動を確認できます。


本章は以上です。次章ではルーティングを説明します。

  1. TypeScriptのコンストラクタは、クラス内にconstructor()で宣言します

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

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

10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?