Angularチュートリアルをやる 6日目
■ 思ったこと
最終章までたどり着いたぞ!!
■ 6. HTTPサービスの有効化
HTTPリクエストを介してHeroService
がデータを取得できるようにしょう!HTTPを通してデータの保存ができるようになるとな、APIとかのことかね?
HttpClient
HTTPを通してリモートサーバーと通信するための仕組み。app.modual.ts
にHttpClientModule
をインポートして、imports
配列にセットすれば準備はOK
In-memory Web APIをインストールしよう
今回は「In-memory Web API」を使ってリモートサーバーとの通信を再現するぞ、モックサーバーみたいなものだと理解!使うにはnpm install angular-in-memory-web-api --save
でパッケージをインストールしよう
app.module.tsにサービスをimport
- インストールしたパッケージから
HttpClientInMemoryWebApiModule
と、これから作成するクラスInMemoryDataService
をあらかじめインポートする imports
配列にはHttpClientInMemoryWebApiModule
にforRoot()
をチェーンして、データ取得元となるInMemoryDataService
を引数に持たせよう
imports: [ … HttpClientModule, HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, { dataEncapsulation: false }), ],
InMemoryDataServiceを作るのだ
- HTTPでデータをやり取りする用のサービスが必要、
ng generate service InMemoryData
でサクっと作ろう - 大事なのは
createDb()
、InMemoryDbService
を実装するべし - heroes.tsで定義していたモックデータを
createDb()
の返り値にすればOK
export class InMemoryDataService implements InMemoryDbService { createDb() { const heroes = [ { id: 1, name: 'warrior' }, { id: 2, name: 'thief' }, { id: 3, name: 'robot' }, { id: 4, name: 'inventor' }, { id: 5, name: 'witch' }, { id: 6, name: 'jester' }, ]; return { heroes }; }
HeroServiceをHTTP対応仕様に変更セヨ
- まずはconstructorに
HttpClient
を追加する - 次にAPIのURLを定義しよう、
createDb()
で設定した戻り値heroes
がURLとなるぞ! - サーバーからデータの取得には
http.get()<>
を使う、引数に設定したエンドポイントにデータを取りに行ってくれるぞ - 今回は
of(HEROES)
をhttp.get<Hero[]>(this.heroesUrl)
に書き換えればOK - すべてのHttpClientメソッドは
Observable
を返すので、戻り値は変わらずObservable<Hero[]>
となる
エラーハンドリングも必要だ
getHeroes()
が失敗することもまぁあるでしょう。失敗したとしても、結果としてObservable
が返ってくれば問題ないのでObservable.pipe()
を使ってどうにかしよう、pipe=パイプ、複数の機能を一個にまとめられる、便利Observable
はpipe()
で値を加工できる(Promiseのthen的な)- 今回は
pipe()
に失敗したObservable
処理用のcatchError
オペレーターを設定しマス - 正常な
Observable
が返ってくれば処理が続くのでcatchError()
の引数にhandleError()
を渡してそこで処理してもらおう
getHeroes(): Observable<Hero[]> { return this.http.get<Hero[]>(this.heroesUrl) .pipe( catchError(this.handleError<Hero[]>('getHeroes', [])) ); }
handleError()を作る
HeroService
の中に作るぞ、エラーを直接触る関数だ!- 汎用的な処理なので、型引数で取得できなかったデータの型を受け取るようにしよう
private handleError<T>(operation = 'operation', result?: T) { // Observable<Hero[]>を返す正常な関数を返す return (error: HttpErrorResponse): Observable<T> => { console.error(error); this.log(`${operation} failed: ${error.message}`); // 空のHero[]を返す return of(result as T); }; }
- consoleの取得結果は「HeroService: getHeroes failed: undefined 」となった
getHeroesをちょこっと改修
tap
オペレーターを使ってログ出力をまとめちゃおtap()
はpipe()
の中で使える、受け取った関数を実行するだけの関数.pipe()
の引数にtap()
とcatchError()
二つ入っていますが、順に処理してくれる。オペレーターは組み合わせて使うのだ!
getHeroes(): Observable<Hero[]> { const heroes = this.http.get<Hero[]>(this.heroesUrl).pipe( tap(() => this.log(`fetched heroes`)), // 戻り値のOperatorFunctionはObservable を返す関数 catchError( // エラー用にメッセージと空配列を渡す this.handleError<Hero[]>('getHeroes', []) ) ); return heroes; }
getHeroも同じく修正
http.get
時に自分のidの画面に渡れるようにしてあげればOK、他はだいたい同じ!- ちょっと待って!自分のidっていうけど…エンドポイントのURLにidは含まれていないのですが!!でも大丈夫、
:baseURL/:id
でidから取得する仕組みがあるみたい
const url = `${this.heroesUrl}/${id}`;
更新機能を追加する
いまだと、せっかく詳細画面で名前を変えてもGo backすると変更は元に戻ってしまう。変更を永続化するには、サーバーに送り返すべし!というわけで、保存ボタンを追加しよう
hero-detail.componentにsave()を作ろう
コンポーネント鉄の掟、「データの取得や保存は行わぬ」がありますので、ここもサービスを呼んで、保存されたら戻るコールバック関数にする
save() { this.heroService.updateHero(this.hero).subscribe(() => this.goBack()); }
サービスの中でupdate!
- http.put()を使う、こいつがサブスクされるとデータが書き換わるとな
- 引数には「URL」「アップデート用のデータ」「オプション(ヘッダ情報)」を設定する
- URLはidつけなくてOK、データから特定されるらしい、かしこいねえ
- これは「Angular in-memory-web-api」の機能で、一意のidがある前提だからみたい
- ヘッダ情報は新しく
new HttpHeaders
してContent-Type
のみ設定している。どんなデータで送信するか伝えるためにも必要
■ 感想
エラー周りの関数の理解がかなりむずかしかった。いや、全部難しいね、http周りの知識が乏しい!ゴールデンウイークは料理したりしてる、世界樹早くやりたい、と待ってる時間が一番楽しいカモネ