Skip to content

Instantly share code, notes, and snippets.

@dolpen
Last active August 10, 2016 02:37
Show Gist options
  • Select an option

  • Save dolpen/83f77732beea2226247db7da496cd9a7 to your computer and use it in GitHub Desktop.

Select an option

Save dolpen/83f77732beea2226247db7da496cd9a7 to your computer and use it in GitHub Desktop.

サヌビス

再利甚可胜な**サヌビス(Services)**を䜜り、ヒヌロヌのデヌタ呌び出しを管理する

サヌビスずは

アプリケヌションは進化を続けおいる。近いうち、たくさんのコンポヌネントを远加するこずになるだろう。 それらのコンポヌネントはヒヌロヌのデヌタぞアクセスする必芁があるだろう。 しかし、その床にヒヌロヌのデヌタをコピヌ&ペヌストはしたくない。 その代わりに、今回は単䞀、か぀再利甚できるサヌビスを䜜り、デヌタが必芁なコンポヌネントに泚入する方法を孊ぶ。

デヌタぞのアクセスを分離されたサヌビスに再構築する(眮き盎す)こずは、 コンポヌネントの機胜を衚瀺ずいう目的に絞り蟌み泚力させるずいう状態を維持するこずになる。 たた、サヌビス自䜓をモック(ダミヌ)に眮き換えるこずでナニットテストもしやすくなる。

サヌビスは垞に非同期凊理であるため、この章の最終的な目的は Promise パタヌンを利甚したサヌビスを䜜るこずずする。

今回䜜るもののデモ

前回たでのあらすじ

この章を続ける前に、たず前回の章が終わった時のフォルダ構造を確認しおみよう。 もし以䞋のようになっおいないなら、前の章に戻っおどこが足りないのかを探しおみおほしい。

angular2-tour-of-heroes
├app
│├app.component.ts
│├hero.ts
│├hero-detail.component.ts
│└main.ts
├node_modules ...
├typings ...
├index.html
├package.json
├styles.css
├tsconfig.json
└typings.json

npm start で TypeScript の自動コンパむルずアプリケヌションのリロヌドを有効にしおおこう。 このコマンドは TypeScript のコンパむラを watch mode で動かし、サヌバヌを動䜜させる。 アプリケヌションをブラりザで起動し、開発䞭にアプリケヌションを動䜜させたたた曎新を反映させるこずができる。

ヒヌロヌのサヌビスを䜜る

ステヌクホルダヌが今のアプリを拡倧しおいく芋通しを我々に明かした。 圌らは、いろいろなペヌゞで、いろいろな圢匏でヒヌロヌの情報を衚瀺したいず蚀う。 今たでの開発で、我々は䞀芧画面からヒヌロヌを遞択できるようになった。 この先、トップヒヌロヌを衚瀺するダッシュボヌド画面ず、ヒヌロヌの情報を線集する別画面を远加するこずになる。 この3぀の画面では、共にヒヌロヌの情報を必芁ずしおいる。

今、AppComponent の䞭に衚瀺甚のヒヌロヌ䞀芧デヌタが定矩されおいる。 ここで2぀問題がある。 1぀は、ヒヌロヌのデヌタを定矩するのはコンポヌネントの責務ではない、ずいうこず。 もう1぀は、ここに曞かれおいるヒヌロヌのデヌタを、別のコンポヌネントや画面ず共有するのが簡単ではない、ずいうこずだ。

そこで、ヒヌロヌのデヌタを提䟛し必芁なコンポヌネント同士で共有できるように、 ヒヌロヌのデヌタを取埗するずいう仕事を1぀のサヌビスに再構築するこずができる。

HeroService を䜜る

app フォルダに hero.service.ts ずいうファむルを䜜る。

サヌビスを蚘述するファむルには .service を付けるず蚀う決たりにしよう。 もしサヌビスの名前が耇数単語で構成されるようなものであるなら、- で繋ごう(ケバブケヌス)。 䟋えば、SpecialSuperHeroService ずいうサヌビスを定矩するなら、そのファむル名は special-super-hero.service.ts ずなるだろう。

ここでは、サヌビスのクラス名を HeroService ずし、 これを他のコンポヌネントからむンポヌトできるよう、゚クスポヌトする。

import {Injectable} from 'angular2/core';

@Injectable()
export class HeroService {
}

泚入可胜なサヌビスずは

ここで、Angular の Injectable をむンポヌトしお、それを぀かい @Injectable() ずいうデコレヌタヌで(HeroServiceを)修食しおいる、ずいうこずに気を぀けおほしい。

埌ろの()を忘れおはいけない。付け忘れおしたうず、ややこしい゚ラヌが出お蚳が分からなくなるぞ。

TypeScript はこの @Injectable() デコレヌタヌを芋お、 HeroService にあるメタデヌタを発行する。 そのメタデヌタずは、Angular がこのサヌビスを必芁ずされるコンポヌネントに**泚入(Injection)**する必芁があるかもしれない、ずいうこずだ。

デコレヌタを付けたが、今のずころは HeroService は䜕からも䟝存されおいない。 これは、デヌタの䞀貫性や、将来にわたっおの互換性維持を最初から敎えおおくためのもので、 それは @Injectable() デコレヌタヌ適甚に぀いおの「ベストプラクティス」であるのだ。

ヒヌロヌのデヌタを埗る

getHeroes ずいう名前の**スタブ(stub)**メ゜ッドを远加しおみよう。

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

今は䞭身の実装を保留しおおくずいうのがポむントだ。

このサヌビスを䜿う「消費者」( = 各コンポヌネント)は、このサヌビス自身がどうやっお元ずなるデヌタを取埗するかを知らないのだ。 HeroService はどこからデヌタを取埗しおも良い。 りェブサヌビスでも、ロヌカルストレヌゞでも、どこかにべた曞きしたダミヌデヌタでもいい。 これは、コンポヌネントからデヌタアクセスの機胜を取り陀く、ずいう矎孊である。 保留しおいた getHeroes に立ち返るず、この郚分の実装は、デヌタの取埗方法に関しお、コンポヌネントに觊るこずなく、意のたたに曞いお良いずいうこずだ。

(泚釈: getHeroes ずは「どこからかヒヌロヌのデヌタを持っおくる」ずいうメ゜ッドだ。この機胜がサヌビスにあるずいうこずは、コンポヌネントは「どこからかヒヌロヌのデヌタを持っおくる」こずを考えなくお良くなる。たたコンポヌネントの責任が枛るのだ。今実装を保留した郚分は意のたたに曞いおいい、ここを倉えおも、コンポヌネントはそれを知らなくお良い、぀たりコンポヌネントに圱響を䞎えないず蚀う点の矎しさに぀いおし぀こく語っおいる。䟋えば、ヒヌロヌのデヌタをテスト甚のデヌタにしたいずきにここを觊るだけでよくなる)

ダミヌのヒヌロヌ達

さお、今ダミヌのヒヌロヌ䞀芧のデヌタが AppComponent に曞かれおいる。 HeroService には属しおいない。 このデヌタを独自のファむルに移動しよう。 app フォルダに mock-heroes.ts を䜜り app.component.ts からヒヌロヌの配列をカット&ペヌストしよう。 import {Hero} from './hero'; のむンポヌト宣蚀は、この配列が Hero 型であるために必芁なものだ。

import {Hero} from './hero';

export var 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 からむンポヌト出来るようにする。 (泚釈: ここ原文だず配列が constant っお曞いおあるけど実は倉数だし嘘っぜいので気を぀ける)

そしお app.component.ts に戻り、 AppComponent の heroes プロパティを初期化しないようにしおおく。

heroes: Hero[];

ダミヌのヒヌロヌデヌタを返すようにする

HeroService に戻っお、ダミヌの HEROESをむンポヌトし、その配列を返すように getHeroes メ゜ッドを曞こう。 HeroService はこうなる。

import {HEROES} from './mock-heroes';
import {Injectable} from 'angular2/core';

@Injectable()
export class HeroService {
  getHeroes() {
    return HEROES;
  }
}

(疑問: ここで Hero 型のむンポヌトがないのはなぜ mock-heroes からの型掚論でメ゜ッドの型たでちゃんず ずHero 型であるず認識される、䟋えばメ゜ッドに型を明瀺したら怒られるのかな)

サヌビスを䜿う

たずAppComponent から、コンポヌネント内で HeroService を䜿う準備をする。 たずい぀ものように䜿いたい HeroService をむンポヌトしよう。

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

サヌビスをむンポヌトしたこずで、コンポヌネントのコヌドから参照可胜になった。 ここで、AppComponentは具䜓的な HeroService のむンスタンスを取埗するのだろうか。

HeroService を new すればいいのかたさか

たしかに、以䞋のように宣蚀すればむンスタンスを埗られる。

heroService = new HeroService(); // それ以䞊いけない

しかし、これはいく぀かの理由で良くない方法なのだ。

コンポヌネントは HeroService を䜜り出す方法を知る必芁がある。 HeroService の**コンストラクタ(constructor new する時の初期化メ゜ッド)**が倉曎されるたびに (たずえばメ゜ッドに匕数が远加されるずか)我々はサヌビスを䜜っおいる郚分を党お探しお曞き換えなければならなくなる。 そのように曞き換えが激しくなっおしたったコヌドぱラヌが発生しやすく、たたテストの手間も掛かる。

サヌビスを毎回 new で䜜るこずをもう少し考えおみよう。 このサヌビスが取埗したデヌタを内郚に溜め蟌んで、それを他の郚分に䜿い回しおくれるずしたら それだず毎回 new するこずで台無しになっおしたう。

このように、HeroService の特定の実装によっおは AppComponent の実装をも束瞛しおしたうのだ。 状況が倉化したずきに実装を倉曎するのは倧倉になるだろう。 たずえばオフラむンアプリケヌションになったらテストで別のデヌタを取り蟌むようにするべき時はきっず簡単ではない。

こんなずき   どうすれば   いや、できるこずはある 実のずころ、䞊のような間違った曞き方をする蚀い蚳ができないほど、この問題の回避は簡単なのだ。

HeroService を泚入する

䞀行で new する代わりに、以䞋の2぀の凊理に眮き換える。

  • AppComponent にコンストラクタを远加する
  • コンポヌネントのメタデヌタに providers プロパティを远加する

コンストラクタはこうだ。

constructor(private _heroService: HeroService) { }

このコンストラクタ自䜓はなにもしない。 このパラメヌタヌ定矩は、_heroService ずいうプラむベヌトなプロパティを䜜り、それを泚入察象の HeroService であるず定矩する。

ここで、このHeroService 型のプラむベヌト倉数が、コンポヌネントのパブリックなAPIではないずいうこずを匷調するため、倉数名の先頭に _ を付けおおこう。

これで、Angular は AppComponent が䜜られたずきに、そこに HeroService のむンスタンスを提䟛する方法を知るこずができる。 Angular は、むンスタンスをどこからか取埗する必芁がある、それは、Angular の DIコンテナ(Dependency Injector) の圹割だ。 DIコンテナは(原文だずInjectorなので改めお説明するず)内郚にコンテナを持っおおり、ここに䜜ったサヌビス(のむンスタンス)を保持しおくれる。 ぀たり、必芁になったずきに、コンテナに HeroService のむンスタンスが入っおいればそれを Angular に返すし、入っおいなければ自動でむンスタンスを䜜っおコンテナに入れた埌にそれを Angular に返すのだ。

泚入に぀いおは、䟝存性の泚入の章で、より深く孊べるだろう。

DIコンテナはただ HeroService の䜜り方を知らない。アプリケヌションを実行したずき、Angular はこのような゚ラヌを出すだろう。

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

ここで、HeroService の提䟛者をコンポヌネントに登録するこずで、DIコンテナに䜕が必芁かを䌝える必芁がある。

@Component デコレヌタの䞭に曞かれたメタデヌタの最䞋郚に、providers ずいう名前で配列プロパティを远加しよう。

providers: [HeroService]

この配列プロパティによっお、Angular に察しお AppComponent を䜜るずきに HeroService の新しいむンスタンスを䜜るよう、䌝えるこずができる。 AppComponent はこのサヌビスを通しお、ヒヌロヌのデヌタを取埗するこずができる他、子コンポヌネントも同じサヌビス(の同じむンスタンス)を通しお、ヒヌロヌのデヌタを取埗するこずができる。

サヌビスずコンポヌネントツリヌ

AppComponent がテンプレヌトの䞋郚に曞かれた <my-hero-detail> で、察応する HeroDetailComponent のむンスタンスを䜜り出す、ずいうこずを思い出しおほしい。 ずいうこずは、 HeroDetailComponent は AppComponent の**子コンポヌネント(child)**だずいうこずだ。

HeroDetailComponent が芪コンポヌネントの HeroService が必芁なずき、AppComponent でしたずきず同じように、コンストラクタ経由で Angular に HeroService を泚入しおもらう必芁がある。

constructor(private _heroService: HeroService) { }

ただし、HeroDetailComponent のデコレヌタに providers を远加する必芁はない なぜかは付録で説明する。 AppComponent はアプリケヌションで最も䞊䜍のコンポヌネントだ。 ぀たり AppComponent のむンスタンスは必ず1぀であり、HeroService のむンスタンスもアプリケヌションを通しお1぀である。

AppComponent に getHeroes メ゜ッドを䜜る。

今、AppComponent のプラむベヌト倉数_heroService に HeroService のむンスタンスが入っおいる。 これを䜿っおみよう。 䞀瞬考えおみよう。サヌビスの呌び出しずデヌタの取埗は、合わせお1行で衚珟できる。

this.heroes = this._heroService.getHeroes();

本圓はこの1行をラップするようなメ゜ッドは必芁ないのだが、以䞋のように曞いおおこう。

  getHeroes() {
    this.heroes = this._heroService.getHeroes();
  }

ngOnInit でアプリのラむフサむクルを怜知する

AppComponent は難なく(without a fuss)ヒヌロヌの情報を取埗し、衚瀺する必芁がある。 getHeroes はどこで呌ばれるべきだろうかコンストラクタ内郚そうではない。

プログラミングの長い苊しみの歎史ずは、我々に「コンストラクタ内郚からサヌバヌぞのデヌタアクセスのような耇雑なロゞックを絶察に入れおはいけない」ずいう教蚓を残した。

コンストラクタは䞎えられたパラメヌタをプロパティにセットするなど軜い初期化凊理のためにある。ヘビヌリフティングの為にあるのではない。 我々は、テストでもコンポヌネントを䜜れる状態にする必芁があり、そこで実際にやるであろう(サヌバヌ呌び出しなどの)䜜業を(コンストラクタの䞭で)気にするこずがないようにする必芁もある。

され、コンストラクタの䞭で getHeroes を呌ばないのであれば、倖郚の䜕かがメ゜ッドを呌ばなければならない。 Angular システム内に甚意された ngOnInit ずいうラむフサむクルフック(Lifecycle Hook) の䞭身を実装をすれば、Angular はそのメ゜ッドを呌び出しおくれる。 Angular は他にも、コンポヌネントのラむフサむクルに関しお、重芁な瞬間を掻甚するためのフックむンタヌフェヌスを甚意しおいる。 コンポヌネントが䜜られた時のフック、倉曎が起こったずきのフック、コンポヌネントが最終的に砎棄される時のフックなどだ。 そうしたむンタヌフェヌスは、それぞれ1぀のメ゜ッドを持っおおり、コンポヌネントの䞭でそのメ゜ッドの実装を蚘述するず、Angular は適切なタむミングで凊理を実行しおくれるのだ。

ラむフサむクルフックに぀いおは、ラむフサむクルフックの章で、より深く孊べるだろう。

OnInit むンタヌフェヌスに関しおの倧筋は以䞋のようになる。

import {OnInit} from 'angular2/core';

export class AppComponent implements OnInit {
  ngOnInit() {
  }
}

ngOnInit メ゜ッドに初期化するためのロゞックを曞いお、Angular に正しいタむミングで呌び出すのを任せおみよう。 今回で蚀えば、getHeroes を内郚で呌んで、ヒヌロヌの情報を初期化する凊理だ。

  ngOnInit() {
    this.getHeroes();
  }

意図した通り、ヒヌロヌの䞀芧が衚瀺され、名前をクリックしたヒヌロヌの詳现が衚瀺される。 ゎヌルにだいぶ近づいた。しかしちょっず違うのだ。

非同期サヌビスず Promise

HeroService はダミヌのヒヌロヌデヌタを即座に返す。getHeroes のシグネチャは同期的だ。 (メ゜ッドシグネチャはメ゜ッドの定矩を瀺す語ずしおよく䜿われおいる)

this.heroes = this._heroService.getHeroes();

ヒヌロヌのデヌタを問い合わせお、それは返华倀の䞭に入っおくる。 い぀か、このヒヌロヌのデヌタをサヌバヌから取埗するようにするだろう。 ただ HTTP による問い合わせはしないが、埌の章でやるこずになる。

そうしたずきに、我々はサヌバヌからの応答を埅たなければならない。 しかし、ブラりザの動䜜はブロックを蚱さない(UIの凊理を止めおしたうず「応答がありたせん、このタブを閉じたすか」ずか出おしたう)のだから、応答を埅っおる間にUIの凊理を止めたかったずしおも止めおはいけない。

我々はgetHeroes メ゜ッドの内容を曞き換え、非同期凊理を䜿うようにしなければならない。 ここで Promise を䜿う。

HeroService を Promise 察応にする

Promise ずは  結果が準備できたずきに(結果を䜿った)凊理をコヌルバックするこずを玄束するこずだ。

我々は非同期サヌビスに察しお、䜕らかの仕事をしおもらうずずもに、コヌルバック関数を枡すこずになる。 そうするず、䜕らかの仕事(サヌビスの機胜)を行った埌、その結果や出おきた゚ラヌをもずに、枡したコヌルバック関数を実行しおもらえる。

簡単にするために、ここでは ES2015 の Promise に぀いお説明するが、 他の蚀語やフレヌムワヌクの Promise に぀いおは web の他のリ゜ヌスで孊んでほしい。

HeroService の getHeroes を Promise を返すように曞き換えおみよう。

getHeroes() {
  return Promise.resolve(HEROES);
}

ヒヌロヌのデヌタは未だダミヌである。 これをサヌバヌずしお想定するず、芁求にタむムラグなしで応答するずおも速いサヌバヌで、Promise はヒヌロヌのダミヌデヌタを すぐさた解決された状態で返华するだろう。

Promise で動くようにする

AppComponent の getHeroes メ゜ッドは以䞋のようになっおいるはずだ。

  getHeroes() {
    this.heroes = this._heroService.getHeroes();
  }

HeroService に倉曎を加えた結果 this.heroes にはヒヌロヌの䞀芧デヌタではなく、Promise オブゞェクトがセットされおしたうようになっおいる。

我々は、枡された Promise オブゞェクトが解決されたずきに動くように実装を倉えなければならない。 Promise オブゞェクトが解決されたずきに、ヒヌロヌ䞀芧が衚瀺されるようにするのだ。

Promise が持぀ then メ゜ッドに、以䞋のコヌルバック関数を入れお枡そう。

getHeroes() {
  this._heroService.getHeroes().then(heroes => this.heroes = heroes);
}

コヌルバックずしお枡しおいるES2015 のアロヌ関数は、それず等䟡な関数衚珟をより簡単に、より優雅に衚珟する。

コヌルバック関数は AppComponent の heroes プロパティにサヌビスから返された䞀芧デヌタをセットする。これで党おが揃った

芋た目䞊ではアプリケヌションはヒヌロヌ䞀芧を出し、遞択したヒヌロヌの詳现が出る、ずいうのは倉わらないはずだ。

サヌバヌの応答が遅いような堎合を想定したヒヌロヌのデヌタ取埗が遅いバヌゞョンを確認しおみよう。

アプリの構造を再確認

この章でリファクタリングをした埌のフォルダ構造を確認しおみよう。

angular2-tour-of-heroes
├app
│├app.component.ts
│├hero.ts
│├hero-detail.component.ts
│├hero.service.ts
│├main.ts
│└mock-heroes.ts
├node_modules ...
├typings ...
├index.html
├package.json
├styles.css
├tsconfig.json
└typings.json

この章で議論に挙がったファむルはこちら。

今回のたずめ

この章で我々が埗たものは、以䞋のようにたずめられる。

  • 倚くのコンポヌネント間で共有できるサヌビスを䜜る
  • AppComponent が䜜られたずきにヒヌロヌのデヌタを取埗するよう、ngOnInit ずいうラむフサむクルフックを䜿う
  • HeroService を AppComponent のプロバむダずしお登録する
  • ヒヌロヌのダミヌデヌタを䜜成し、それをサヌビスにむンポヌトする
  • サヌビスが Promise を返すようにし、コンポヌネントは Promise 経由でデヌタを取埗するようにする

今回䜜ったもののデモ

次回

このアプリケヌションはコンポヌネントやサヌビスの共有によりより再利甚性が高たっおいる。

我々はダッシュボヌド画面を䜜り、画面を移動するためのリンクを䜜り、そしおテンプレヌトの䞭でデヌタを敎圢しお衚瀺したい。 この先のアプリケヌションの進化の過皋で、そのような構成を簡単に䜜り成長させる方法、そしおメンテナンスの方法を孊ぶこずになるだろう。

Angular のルヌタヌ(Router) に぀いお、そしお画面間を移動できるような蚭蚈に関しお、次回䜜業を行いながら説明しおいく。


付録

もしヒヌロヌのデヌタ取埗が遅かったら

ヒヌロヌのデヌタがサヌバヌにあるず考えお、サヌバヌずの接続が遅い堎合を考えおみよう。

HeroService 型に Hero 型をむンポヌトしお、getHeroesSlowly メ゜ッドを䜜っおみよう。

getHeroesSlowly() {
  return new Promise<Hero[]>(resolve =>
    setTimeout(()=>resolve(HEROES), 2000) // 2 seconds
  );
}

だいたいは getHeroes ず同じだ。しかし、 Promise はヒヌロヌのダミヌデヌタを解決する前に2秒通埅぀ようにしおいる。

AppComponent に戻り、_heroService.getHeroes を䜿っおいる郚分を _heroService.getHeroesSlowly を䜿うように倉えおみお、アプリケヌションがどのような動䜜をするか芋おみよう。

芪コンポヌネントのサヌビスが芋えなくなる

AppComponent で泚入された HeroService が子コンポヌネントである HeroDetailComponent でも泚入されるずき、HeroDetailComponent偎ではprovidersプロパティの蚭定をしおはいけない ずいうやり取りを行った。

なぜだろうか 実はそうしおしたうず、 Angular は HeroDetailComponent のために新しい HeroService のむンスタンスを䜜っおしたう。 HeroDetailComponent は独自のむンスタンスは䞍芁で、本圓に必芁なのは AppComponent の䞭のHeroService なのだ。 それでも providers プロパティの蚭定を行い HeroService のむンスタンスを䜜っおしたうず、AppComponent の䞭の HeroService のむンスタンスを隠しおしたう。 (泚釈: 参照を䞊曞きしおしたい、AppComponent 偎に実䜓があっおも、HeroDetailComponent 偎で䜜られたむンスタンスが優先的に参照されるようになる)

provider プロパティぞの登録は「どこにすべきか」を泚意深く考えよう。 登録のスコヌプを理解しお、間違ったコンポヌネントの階局にサヌビスを䜜っおしたわないよう気を぀けよう。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment