[超初心者が行く] Angular5 Tutorialしてみた6

Angular5 Tutorial サービスを使う!

Angular – Tutorial: Tour of Heroes を翻訳(意訳多め)しています。

[超初心者が行く] Angular5 Tutorialしてみた5の続き

前回は、コンポーネントを機能ごとにわけてアプリ開発しやすくしました。

さて、今回はサービスを使って呼び出し可能なデータを作りましょう!

ここからが翻訳になります。

ビックリマークの部分は個人的にわからない用語の意味を載せています。
このように、本家Angularのサイトだけではわからない部分が多いため、本家以外の説明も載せているため、どこからどこまでが本家の情報なのか線引ができなくなっています。ご了承ください。

では、チュートリアルやっていきましょう!

サービス

現在、Tour of Heroes アプリのHeroesComponentは前もって作成したサンプルデータを取得して表示しています。

このチュートリアルでのリファクタリングの後、HeroesComponentはリーンになり、インターフェースの見栄えのみ担うことになるでしょう。そして、モック・サービスで単体テストを行うときに簡単になるでしょう。

リーン
製造業を中心に展開されているリーン生産方式の考え方(リーン思考)をソフトウェア製品に適用した開発手法。アジャイルソフトウェア開発手法の1つに数えられる。
アジャイルソフトウェア開発手法
ソフトウェア要求仕様の変更などの変化に対して機敏な対応ができ、顧客に価値あるソフトウェアを迅速に提供することを目的とするソフトウェア開発方法論の総称。特に「アジャイルソフトウェア開発宣言」に合意しているもの、「アジャイルアライアンス」に参加しているものを指す。アジャイル(agile)とは「俊敏な」「機敏な」という意味で、軽量型(ライトウェイト)開発ともいう。
モックオブジェクト(Mock Object)とは、ソフトウェアテスト時、特にテスト駆動開発ビヘイビア駆動開発における代用の下位モジュールスタブの一種。スタブと比較して、検査対象のモジュールがその下位モジュールを正しく利用しているかどうかを検証するのに使われる。
ソフトウェアテスト
ソフトウェアテスト(software test)は、コンピュータのプログラムから仕様にない振舞または欠陥(バグ)を見つけ出す作業のことである。
テスト駆動開発
テスト駆動開発 (てすとくどうかいはつ、test-driven development; TDD) とは、プログラム開発手法の一種で、プログラムに必要な各機能について、最初にテストを書き(これをテストファーストと言う)、そのテストが動作する必要最低限な実装をとりあえず行った後、コードを洗練させる、という短い工程を繰り返すスタイルである。多くのアジャイルソフトウェア開発手法、例えばエクストリーム・プログラミングにおいて強く推奨されている。近年はビヘイビア駆動開発へと発展を遂げている。
スタブ
スタブ (stub) とは、コンピュータプログラムのモジュールをテストする際、そのモジュールが呼び出す下位モジュールの代わりに用いる代用品のこと。下位モジュールが未完成でも代わりにスタブを用いることでテストが可能になる。

なぜサービスを使う?

ユーザーがアプリを使うことを念頭に置いた場合、ユーザーはあれこれデータを変更して、そのデータを表示することを期待するでしょう。しかし、コンポーネントはデータを直接取得または保存すべきではありません。そのため、データアクセスによって取得したデータの表示と保存はサービスを使うことになります。

このチュートリアルでは、すべてのアプリケーションクラスがサンプルのヒーローデータを取得するために使用するHeroServiceを作成します。 そのサービスを新しく作成するのではなく、Angular dependency injectionを使ってHeroesComponentコンストラクタに組込みます。

サービスは、お互い結びつきがないクラス間で情報を共有するのに最適な方法です。 MessageServiceを作成し、2つの機能をもたせます:

  1. HeroServiceはメッセージを送信します。
  2. そのメッセージを表示するMessagesComponentに格納します。
Angular dependency injection
Dependency Injection(DI)は、他のオブジェクトに依存するオブジェクトを作成する方法です。 Dependency Injectionシステムは、オブジェクトのインスタンスを作成するときに依存関係の依存オブジェクトを提供します。
インスタンス
インスタンスとは、英語において「事実」「実例」「実態」といった意味を示す名詞である。プログラミングの分野では、オブジェクト指向言語における具体的な(クラスが生成する)オブジェクトを指す語として用いられる。
コンストラクタ
コンストラクタ(構築子、英: Constructor)は、オブジェクト指向のプログラミング言語で新たなオブジェクトを生成する際に呼び出されて内容の初期化などを行なう関数、メソッドのことである。

HeroServiceを作成する

Angular CLIを使用して、heroサービスを作成します。

コマンド ng generate service hero
このコマンドは、src/app/hero.service.tsにスケルトンHeroServiceクラスを生成します。HeroServiceクラスは以下のようになります。
src/app/hero.service.ts (new service)

@Injectable() サービス

新しいサービスはInjectableをインポートし、そのクラスに@Injectable()デコレータがあることを注目してください。

@Injectable()デコレータは、このサービス自体が依存関係を有する可能性があることをAngularに通知します。 今は依存関係はありませんが、間もなく有するようになるでしょう。 もし、なくてもデコレータを組込み、慣れて置いたほうが良いでしょう。

Angularスタイルガイドラインでは、デコレータを組込みこむことを強くお勧めします。リンターはこの規則を有します。

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

HeroServiceは、Webサービス、ローカルストレージ、モックデータソースなど、どこからでもヒーローデータを取得できます。

コンポーネントに触れることなく、コンポーネントのデータアクセスを削除することで、いつでも実装に関する考え方を変えることができます。 コンポーネントはサービスがどのように機能するか関係がありません。

このチュートリアルのアプリでは、引き続きサンプルのデータを表示します。

HeroServiceにヒーロークラスとヒーローズクラスをインポートします。

そして、サンプルのデータを返すgetHeroesメソッドを追加します。

src/app/hero.service.ts

HeroServiceを設定する

DIシステムでHeroServiceを設定してから、HeroesComponentに組込むようにする必要があります。

HeroServiceを設定するには、HeroesComponent、AppComponent、AppModuleのいくつかの方法があります。 各オプションには長所と短所があります。

このチュートリアルでは、AppModuleでの設定を選択します。

これは、CLIに自動的に設定するようにした場合によく使われるコマンドです。

サービス作成時にオプション「–module = app」を追加します。

コマンド ng generate service hero –module=app

手動で設定する方法もあります。

AppModuleクラスを開き、HeroServiceをインポートと、@NgModule.providers配列にプロバイダーを追加します。

import { HeroService } from ‘./hero.service’;
providers: [ HeroService, MessageService ],

コマンドを使えば不要です。

src/app/app.module.ts (providers)

providers配列は、HeroServiceの単一の共有インスタンスを作成し、それを要求する任意のクラスに組込むようにAngularに指示します。

HeroServiceは、HeroesComponentに注入できる状態になりました。

providersの詳細については、プロバイダガイドを参照してください。

HeroesComponentを更新する

HeroesComponentクラスファイルを開きます。

HEROESのインポートはHeroServiceで行うため必要ありません。削除し、代わりにHeroServiceをインポートしてください。

import { HeroService } from ‘../hero.service’;

heroesプロパティを単純な定義の宣言に置き換えます。

heroes: Hero[];
HeroServiceを注入する

HeroService型のprivate heroServiceパラメータをコンストラクタに追加します。

constructor(private heroService: HeroService) { }

このパラメータは、プライベートheroServiceプロパティを同時に定義し、HeroServiceが組込まれたクラスとして識別します。

AngularがHeroesComponentを作成すると、DIシステムはheroServiceパラメータをHeroServiceの単独インスタンスに設定します。

getHeroes()を追加する

サービスからヒーローを取得する関数を加えます。

getHeroes(): void { this.heroes = this.heroService.getHeroes(); }
ngOnInitで呼び出す

コンストラクタでgetHeroes()を呼び出すことはできますが、それはベストプラクティスではありません。

ベストプラクティス
ベストプラクティス(英: best practice)は、ある結果を得るのに最も効率のよい技法、手法、プロセス、活動などのこと。最善慣行、最良慣行と訳されることもある。

本来、コンストラクタは何もするべきではありません。

プロパティへのコンストラクタパラメータの受け渡しなど、簡単な初期化のためにコンストラクタを使用します。実際のデータサービスと同じように、リモートサーバーへのHTTP要求を行う関数を呼び出すべきではありません。

代わりに、ngOnInitライフサイクルフック内でgetHeroes()を呼び出し、HeroesComponentインスタンスを作成した後、適切なタイミングでAngularはngOnInitを呼び出します。

ngOnInit() { this.getHeroes(); }
動作を確認する

ブラウザが更新された後、アプリは前と同じように実行され、ヒーローのリストと、ヒーロー名をクリックするとヒーローの詳細ビューが表示されます。

src/app/heroes/heroes.component.ts (import HeroService)

Observableオブジェクト

HeroService.getHeroes()メソッドには同期サインがあり、HeroServiceがヒーローを同期的に取得できることを意味します。 HeroesComponentは、ヒーローを同期的に読み込みできるかのように、getHeroes()の結果を表示します。

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

これは実際のアプリでは機能しません。 サービスは現在サンプルデータを返すので、動作しています。 しかし、本格的なアプリは非同期的にリモートサーバーからヒーローを取得する必要があります。

HeroServiceはサーバが応答するのを待たなければならず、getHeroes()はヒーローデータをすぐに返すことができず、ブラウザはサービスが同期待ちしている間場合、表示しなくなります。

HeroService.getHeroes()には何らかの非同期のサインが必要です。

非同期のサインはコールバックを取ることができます。 Promiseオブジェクトや、Observableオブジェクトも返すことができます。

このチュートリアルでは、HeroService.getHeroes()はObservableオブジェクトを部分的に返します。これはAngularがHttpClient.getメソッドを使用してヒーローを取得し、HttpClient.get()がObservableオブジェクトを返すためです。

コールバック

コールバック(英: Callback)とは、プログラミングにおいて、他のコードの引数として渡されるサブルーチンである。これにより、低レベルの抽象化層が高レベルの層で定義されたサブルーチン(または関数)を呼び出せるようになる。

Promiseオブジェクト
Promiseオブジェクトは非同期処理の最終的な完了処理(もしくは失敗)およびその結果の値を表現します。
Observableオブジェクト

Observable HeroService

Observableは、RxJSライブラリの重要なクラスの1つです。

HTTPの後のチュートリアルでは、AngularのHttpClientメソッドがRxJS Observablesを返すことを学びます。 このチュートリアルでは、RxJS of()関数を使用してサーバーからデータを取得する方法をシミュレートします。

HeroServiceファイルを開き、ObservableとRxJSのシンボルをインポートします。

import { Observable } from ‘rxjs/Observable’;
import { of } from ‘rxjs/observable/of’;

getHeroesメソッドを次のメソッドに置き換えます。

getHeroes(): Observable<Hero[]> { return of(HEROES); }

of(HEROES)はObservable<Hero>を返します。これはサンプルヒーロの配列です。

HeroesComponentでsubscribe

HeroService.getHeroesメソッドはHero[]を返すために使用されます。 Observable <Hero>]を返します。

HeroesComponentをそれに適応させる必要があります。

getHeroesメソッドを探し、次のコードに置き換えます(比較のために前のバージョンと並べて表示)

変更前 getHeroes(): void { this.heroes = this.heroService.getHeroes(); }
変更後 getHeroes(): void { this.heroService.getHeroes() .subscribe(heroes => this.heroes = heroes); }
Observable.subscribe()はクリティカルな違いです。

変更前では、ヒーローの配列がコンポーネントのheroesプロパティに割り当てられています。 この割り当てでは同期的に受け渡しを行うため、サーバがヒーローを即座に返すこともありますが、ブラウザがサーバの応答を待っている間にUIをフリーズさせる恐れもあります。

つまり、HeroServiceが実際にリモートサーバーの要求を待っているときは機能しません。

変更後ではObservableがヒーローの配列が受け渡されるのを待っています。これは現在または数分後に起こる可能性があります。 次に、subscribeは、受け取った配列をコールバックに渡し、コンポーネントのheroesプロパティを設定します。

この非同期アプローチは、HeroServiceがサーバーからヒーローを要求するときに機能します。

メッセージを表示する

このセクションでは、

画面の下部にアプリケーションメッセージを表示するMessagesComponentを追加します。
アプリケーション全体に組み込み可能なメッセージを送信するためのMessageServiceを作成します。
MessageServiceをHeroServiceに挿入し、HeroServiceがヒーローを正常に取得したときにメッセージを表示します。

MessagesComponentを作成する

CLIを使用してMessagesComponentを作成します。

コマンド ng generate component messages

CLIはsrc/app/messagesフォルダにコンポーネントファイルを作成し、AppModuleにMessagesComponentを宣言します。

AppComponentテンプレートを変更して、生成されたMessagesComponentを表示します。

/src/app/app.component.html

ページの下部にMessagesComponentのデフォルトが表示されます。

MessageServiceを作成する

CLIを使用してsrc/appにMessageServiceを作成します。 –module = appオプションは、このサービスをAppModuleに宣言するようCLIに指示します。

コマンド ng generate service message module=app

MessageServiceを開き、その内容を次のように置き換えます。

/src/app/message.service.ts

このサービスは、キャッシュにメッセージを追加するadd()と、キャッシュをクリアするclear()する2つの機能を有します。

HeroServiceに注入する

HeroServiceを再度開き、MessageServiceをインポートします。

import { MessageService } from ‘./message.service’;

プライベートなmessageServiceプロパティを宣言するパラメータを使用するため、コンストラクタを変更します。 Angularは、HeroServiceを作成するときにそのプロパティにシングルトンのMessageServiceを挿入します。

シングルトン

Singleton パターン(シングルトン・パターン)とは、オブジェクト指向のコンピュータプログラムにおける、デザインパターンの1つである。GoF(Gang of Four; 4人のギャングたち)によって定義された。Singleton パターンを用いると、そのクラスのインスタンスが1つしか生成されないことを保証することができる。ロケールやLook&Feelなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される。

Singleton パターン – Wikipedia

constructor(public messageService: MessageService) {}

これは典型的な “サービス中のサービス”になります。HeroesComponentに注入されたHeroServiceにMessageServiceを注入します。

HeroServiceからメッセージを送信する

ヒーローが読み込まれたときにメッセージを送信するようにgetHeroesメソッドを変更します。

getHeroes(): Observable<Hero[]> {
// Todo: send the message _after_ fetching the heroes
this.messageService.add(‘HeroService: fetched heroes’);
return of(HEROES);
}
HeroServiceからのメッセージを表示する

MessagesComponentは、HeroServiceがヒーローを取得したときに送信されたメッセージを含め、すべてのメッセージを表示する必要があります。

MessagesComponentを開き、MessageServiceをインポートします。

import { MessageService } from ‘../message.service’;

public messageServiceプロパティを宣言するパラメータを使用してコンストラクタを変更します。 Angularは、HeroServiceを作成するときにそのプロパティにシングルトンのMessageServiceを挿入します。

constructor(public messageService: MessageService) {}

messageServiceプロパティは、テンプレート内でバインドしようとしているのでpublicである必要があります。

 Angularはパブリックコンポーネントのプロパティにのみバインドされます。
MessageServiceにバインドする

CLIによって生成されたMessagesComponentテンプレートを次のものに置き換えます。

 src/app/messages/messages.component.html

このテンプレートは、messageServiceコンポーネントに直接バインドされます。

  • *ngIfは、表示するメッセージがある場合にのみ、メッセージ領域を表示します。
  • *ngForは、繰り返される<div>要素のメッセージのリストを表示します。
  • Angularイベントバインディングは、ボタンのクリックイベントをMessageService.clear()にバインドします。

下の「最終的なコードレビュー」に記載されている、プライベートCSSスタイルmessages.component.cssに追加すると、メッセージの見栄えが良くなります。

ブラウザが更新され、ページにヒーローのリストが表示されます。 下にスクロールすると、メッセージエリアのHeroServiceからのメッセージが表示されます。 「クリア」ボタンをクリックすると、メッセージ領域が消えることになります。

最終的なコードレビュー

このページで解説されているコードファイルは次のとおりです。アプリはこのライブサンプル/ダウンロードの例のようになります。

src/app/hero.service.ts

src/app/message.service.ts

src/app/heroes/heroes.component.ts

src/app/messages/messages.component.ts

src/app/messages/messages.component.html

src/app/messages/messages.component.css

src/app/app.component.html

まとめ

  • HeroServiceクラスへのデータアクセスをリファクタリングしました。
  • AppModuleにHeroServiceを宣言し、どこにでも注入できるようにしました。
  • Angular Dependency Injectionを使用してコンポーネントにサービスを挿入しました。
  • HeroService get dataメソッドを用いて非同期的に受け渡しました。
  • ObservableとRxJS Observableライブラリを使用しました。
  • RxJS of()を使って、疑似ヒーロのObservable(Observable <Hero []>)を返しました。
  • コンストラクタではなく、コンポーネントのngOnInitライフサイクルフックは、HeroServiceメソッド使って呼び出します。
  • クラス間の結びつきのないのMessageServiceを作成しました。
  • コンポーネントに注入されたHeroServiceは、別の注入サービスMessageServiceで作成されます。

次回はルーティング

今回は、サービスを使ってデータの受け渡しの機能を作りました。

正直、さっぱりわかりません。感覚的にわかった気になるくらいで止まっています。

なんなんだ、Observable・・・

さて、気を取り直して次のルーティングに進みましょう!

シェアする

  • このエントリーをはてなブックマークに追加

フォローする