Angular2の始め方。Angular2 公式チュートリアル – Services(簡単な和訳)
2017.04.03
この記事は最終更新日から1年以上が経過しています。
SERVICES
こちらのページは、以下のページの簡単な和訳です。
前回
Angular2の始め方。Angular2 公式チュートリアル – Multiple Components(簡単な和訳)
その他
Angular2の始め方。Angular2 公式チュートリアル – Master/Detail(簡単な和訳)
Angular2の始め方。Angular2 公式チュートリアル – The Hero Editor(簡単な和訳)
ヒーローデータコールを管理するための再利用可能なサービスを作成します。
Tour of Heroesアプリが進化するにつれて、ヒーローデータにアクセスする必要のあるコンポーネントを追加します。
同じコードを繰り返しコピーして貼り付ける代わりに、再利用可能な単一のデータサービスを作成し、それを必要とするコンポーネントに注入します。
別のサービスを使用すると、コンポーネントの傾きがなくなり、ビューのサポートに専念できるようになり、模擬サービスでコンポーネントを単体テストすることが容易になります。
データサービスは常に非同期なので、データサービスのPromiseベースのバージョンでページを終了します。
このページが終了したら、アプリはこの live example / downloadable example.のようになります。
Where We Left Off
ヒーローズツアーを続ける前に、以下の構造を持っていることを確認しましょう。そうでない場合は、前の章に戻って行く必要があります。
angular-tour-of-heroes ├──src │ ├──app │ │ ├──app.component.ts │ │ ├──app.module.ts │ │ ├──hero.ts │ │ └──hero-detail.component.ts │ ├──main.ts │ ├──index.html │ ├──styles.css │ ├──systemjs.config.js │ └──tsconfig.json ├──node_modules ... └──package.json
Keep the app transpiling and running
次のコマンドを入力します。
npm start
このコマンドは、”watch mode”で、TypeScriptコンパイラを実行し、コードが変更されたときに自動的に再コンパイルします。
このコマンドは、ブラウザでアプリを同時に起動し、コードが変更されたときにブラウザを更新します。
ブラウザを再コンパイルまたは更新するために一時停止することなく、ヒーローのツアーを構築し続けることができます。
Creating a hero service
ステークホルダーは、ヒーローをさまざまな方法でさまざまなページに表示したいと考えています。
ユーザーはすでにリストからヒーローを選択することができます。
すぐに、最高のヒーローを持つダッシュボードを追加し、ヒーローの詳細を編集するための別のビューを作成します。
3つのビューには、ヒーローデータが必要です。
現時点では、AppComponentは表示用の模擬ヒーローを定義しています。しかし、ヒーローを定義することはコンポーネントの仕事ではなく、ヒーローのリストを他のコンポーネントやビューと簡単に共有することはできません。
このページでは、ヒーローデータ取得ビジネスをデータを提供する単一のサービスに移行し、そのサービスをデータを必要とするすべてのコンポーネントと共有します。
Create the HeroService
hero.service.tsという名前のアプリケーションフォルダにファイルを作成します。
“““
サービスファイルの命名規則は、小文字のサービス名の後に.serviceを付けたものです。
複数語のサービス名の場合は、lower dash-case を使用します。
たとえば、SpecialSuperHeroServiceのファイル名は、special-super-hero.service.tsとなります。
“““
HeroServiceクラスに名前を付け、他がインポートできるようにエクスポートします。
src/app/hero.service.ts (starting point)
import { Injectable } from '@angular/core'; @Injectable() export class HeroService { }
Injectable Services
Angular Injectable関数をインポートし、その関数を@Injectable()デコレータとして適用したことに注目してください。
“““
カッコを忘れないでください!それらを無視すると、診断が難しいエラーが発生します。
“““
@Injectable()デコレータは、サービスに関するメタデータを出力するようにTypeScriptに指示します。
メタデータは、Angularが他の依存関係をこのサービスに注入する必要があることを指定します。
HeroServiceは現時点では依存関係はありませんが、先頭から@Injectable()デコレータを適用すると、一貫性と将来性が保証されます。
Getting Heroes
getHeroes関数に、スタブを追加します。
@Injectable() export class HeroService { getHeroes(): void {} // stub }
HeroServiceは、Webサービス、ローカルストレージ、または模擬データソースのどこからでもHeroデータを取得できます。
コンポーネントからのデータアクセスを削除すると、ヒーローデータを必要とするコンポーネントに触れることなく、いつでも実装に関する考えを変えることができます。
Move the mock hero data
app.component.tsからHEROES配列を切り取り、appフォルダのmock-heroes.tsという新しいファイルに貼り付けます。
さらに、ヒーロー配列がHeroクラスを使用するため、import {Hero} …ステートメントをコピーします。
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など他の場所でもインポートできるようにエクスポートされます。
app.component.tsでは、HEROES配列を切り取り、初期化されていない英雄のプロパティを追加します。
src/app/app.component.ts (heroes property)
heroes: Hero[];
Return mocked hero data
HeroServiceに戻り、模擬HEROESをインポートし、getHeroes()メソッドから返します。 HeroServiceは次のようになります。
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; } }
Import the hero service
HeroServiceを他のコンポーネント(AppComponentから)で使用する準備が整いました。
HeroServiceをインポートして、コード内でHeroServiceを参照できるようにします。
src/app/app.component.ts (hero-service-import)
import { HeroService } from './hero.service';
Don’t use new with the HeroService
AppComponentは、実行時の具体的なHeroServiceインスタンスをどのように取得する必要がありますか?
次のようにHeroServiceの新しいインスタンスを作成することができます。
heroService = new HeroService(); // don't do this
ただし、このオプションは理想的ではありません。理由は次のとおりです。
コンポーネントは、HeroServiceを作成する方法を知っていなければなりません。
HeroServiceコンストラクターを変更する場合は、サービスを作成したすべての場所を見つけて更新する必要があります。
複数の場所にコードをパッチすると、エラーが発生しやすくなり、テストの負担が増します。
新しいサービスを使用するたびにサービスを作成します。
サービスが他とキャッシュする、ヒーローやシェアをキャッシュすればどうなるでしょうか?
あなたはそれをすることができませんでした。
AppComponentがHeroServiceの特定の実装にロックされているため、オフラインでの操作やテスト用に異なる模擬バージョンの使用など、さまざまなシナリオの実装を切り替えるのは難しいでしょう。
Inject the HeroService
新しい行を使用する代わりに、2行を追加します。
プライベートプロパティも定義するコンストラクタを追加します。
コンポーネントのプロバイダのメタデータに追加します。
コンストラクタを追加します。
src/app/app.component.ts (constructor)
constructor(private heroService: HeroService) { }
コンストラクタ自体は何もしません。このパラメータは、プライベートheroServiceプロパティを同時に定義し、HeroService注入サイトとして識別します。
Now Angularは、AppComponentを作成するときにHeroServiceのインスタンスを提供することを知っています。
“““
依存性注入の詳細については、依存性注入ページを参照してください。
“““
インジェクタは、HeroServiceの作成方法をまだ分かりません。今すぐコードを実行した場合、Angularはこのエラーで失敗します。
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
インジェクタにHeroServiceの作成方法を教えるには、@Componentsのコンポーネントメタデータの一番下に、プロバイダ配列プロパティを追加します。
src/app/app.component.ts (providers)
providers: [HeroService]
providers配列は、AppComponentを作成するときにHeroServiceの新しいインスタンスを作成するようにAngularに指示します。
AppComponentとその子コンポーネントは、そのサービスを使用してヒーローデータを取得できます。
getHeroes() in the AppComponent
サービスはheroServiceプライベート変数にあります。
あなたはサービスを呼び出して、1行でデータを取得することができます。
this.heroes = this.heroService.getHeroes();
あなたは本当に1行をラップするための専用メソッドを必要としません。とにかくそれを書きましょう。
src/app/app.component.ts (getHeroes)
getHeroes(): void { this.heroes = this.heroService.getHeroes(); }
The ngOnInit lifecycle hook
AppComponentはヒーローデータをfetchして表示する必要があります。
コンストラクタでgetHeroes()メソッドを呼び出すように誘惑されるかもしれませんが、コンストラクタには複雑なロジック、特にデータアクセスメソッドなどのサーバーを呼び出すコンストラクタが含まれてはいけません。
コンストラクタは、プロパティへの配線コンストラクタパラメータのような単純な初期化用です。
AngularコールgetHeroes()を使用するには、Angular ngOnInitライフサイクルフックを実装できます。
Angularは、コンポーネントライフサイクルのクリティカルな瞬間を、作成時、変更後、最終的に破棄するためのインターフェイスを提供します。
各インタフェースには1つのメソッドがあります。コンポーネントがそのメソッドを実装すると、適切なタイミングでAngularがそのメソッドを呼び出します。
“““
ライフサイクルフックについては、Lifecycle Hooks(ライフサイクルフック)ページで。
“““
OnInitインターフェイスの基本的な概要は次のとおりです(これをコードにコピーしないでください)。
import { OnInit } from '@angular/core'; export class AppComponent implements OnInit { ngOnInit(): void { } }
OnInitインターフェイスの実装をエクスポートステートメントに追加します。
export class AppComponent implements OnInit {}
初期化ロジックを内部に持つngOnInitメソッドを記述します。
Angularは適切なタイミングで呼び出します。この場合、getHeroes()を呼び出して初期化します。
app/app.component.ts (ng-on-init)
ngOnInit(): void { this.getHeroes(); }
ヒーロー名をクリックすると、ヒーローのリストとヒーローの詳細ビューが表示され、アプリケーションは期待通りに動作するはずです。
Async services and Promises
HeroServiceはモックヒーローのリストをすぐに返します。そのgetHeroes()シグネチャは同期しています。
this.heroes = this.heroService.getHeroes();
最終的に、主人公のデータはリモートサーバーから取得されます。
リモートサーバーを使用する場合、ユーザーはサーバーが応答するのを待つ必要はありません。さらに、待機中にUIをブロックすることはできません。
ビューをレスポンスと調整するには、Promisesを使用します。
これは、getHeroes()メソッドのシグネチャを変更する非同期手法です。
The hero service makes a Promise
Promiseは、本質的に結果の準備ができたときにコールバックすることを約束します。
非同期サービスに何らかの作業を行い、コールバック関数を与えるように要求します。
サービスはその作業を行い、最終的には結果またはエラーで関数を呼び出します。
“““
これは簡単な説明です。 ES2015の「Promises for asynchronous programming」ページの「Exploring ES6」のページをご覧ください。
“““
このPromiseを返すgetHeroes()メソッドでHeroServiceを更新します。
src/app/hero.service.ts (excerpt)
getHeroes(): Promise<Hero[]> { return Promise.resolve(HEROES); }
あなたはまだデータを嘲笑しています。
即座に解決されたPromiseを結果として模擬ヒーローと返すことで、超高速、ゼロ遅延サーバーの動作をシミュレートしています。
Act on the Promise
HeroServiceへの変更の結果、this.heroesは英雄の配列ではなくPromiseに設定されます。
src/app/app.component.ts (getHeroes – old)
getHeroes(): void { this.heroes = this.heroService.getHeroes(); }
Promiseが解決したらそれを実行するように実装を変更する必要があります。Promiseが成功すると、あなたはヒーローを表示します。
Promiseのthen()メソッドへの引数としてコールバック関数を渡します。
src/app/app.component.ts (getHeroes – revised)
getHeroes(): void { this.heroService.getHeroes().then(heroes => this.heroes = heroes); }
“““
Arrow functions での説明したように、コールバックのES2015 arrow functionは、同等の関数式よりも簡潔であり、これを正常に処理します。
“““
コールバックは、コンポーネントのheroesプロパティをサービスが返すヒーローの配列に設定します。
アプリはまだ実行中で、ヒーローのリストを表示し、詳細ビューで名前選択に応答します。
“““
このページの最後に、Appendix: take it slow で、アプリケーションの接続状況が悪いと説明しています。
“““
Review the app structure
すべてのリファクタリング後に次の構造を持っていることを確認します。
angular-tour-of-heroes ├──src │ ├──app │ │ ├──app.component.ts │ │ ├──app.module.ts │ │ ├──hero.ts │ │ ├──hero-detail.component.ts │ │ ├──hero.service.ts │ │ └──mock-heroes.ts │ ├──main.ts │ ├──index.html │ ├──styles.css │ ├──systemjs.config.js │ └──tsconfig.json ├──node_modules ... └──package.json
このページで解説するコードファイルは次のとおりです。
src/app/hero.service.ts
import { Injectable } from '@angular/core'; import { Hero } from './hero'; import { HEROES } from './mock-heroes'; @Injectable() export class HeroService { getHeroes(): Promise<Hero[]> { return Promise.resolve(HEROES); } }
src/app/app.component.ts
import { Component, OnInit } from '@angular/core'; import { Hero } from './hero'; import { HeroService } from './hero.service'; @Component({ selector: 'my-app', template: ` <h1>{{title}}</h1> <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <hero-detail [hero]="selectedHero"></hero-detail> `, styles: [` .selected { background-color: #CFD8DC !important; color: white; } .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes li.selected:hover { background-color: #BBD8DC !important; color: white; } .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } .heroes .text { position: relative; top: -3px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; } `], providers: [HeroService] }) export class AppComponent implements OnInit { title = 'Tour of Heroes'; heroes: Hero[]; selectedHero: Hero; constructor(private heroService: HeroService) { } getHeroes(): void { this.heroService.getHeroes().then(heroes => this.heroes = heroes); } ngOnInit(): void { this.getHeroes(); } onSelect(hero: Hero): void { this.selectedHero = hero; } }
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'} ];
The road you’ve travelled
このページであなたが達成したことは次のとおりです。
・多くのコンポーネントで共有できるサービスクラスを作成しました。
・AppComponentがアクティブになったときにヒーローデータを取得するには、ngOnInitライフサイクルフックを使用しました。
・HeroServiceをAppComponentのプロバイダとして定義しました。
・あなたは模擬ヒーローデータを作成し、それらをサービスにインポートしました。
・Promiseを返すようにサービスを設計し、Promiseからデータを取得するコンポーネントを設計しました。
あなたのアプリはこのlive example / downloadable example. のように見えるはずです。
The road ahead
Tour of Heroesは、共有コンポーネントとサービスを使用して、より再利用可能になっています。
次の目標は、ダッシュボードを作成し、ビュー間をルーティングするメニューリンクを追加し、テンプレート内のデータをフォーマットすることです。
アプリケーションが進化するにつれて、あなたはそれを設計してより容易に成長させ、維持する方法を発見するでしょう。
次のチュートリアルページでは、Angular component router と navigation について読んでください。
Appendix: Take it slow
app/hero.service.ts (getHeroesSlowly)
getHeroesSlowly(): Promise<Hero[]> { return new Promise(resolve => { // Simulate server latency with 2 second delay setTimeout(() => resolve(this.getHeroes()), 2000); }); }
getHeroes()と同様に、Promiseも返されます。しかし、この約束は2秒待ってから、擬人ヒーローとの約束を解決します。
AppComponentに戻り、getHeroes()をgetHeroesSlowly()に置き換え、アプリケーションの動作を確認します。