ペロハム大学

ヤルゾー

Angularチュートリアルをやる 6日目

■ 思ったこと

最終章までたどり着いたぞ!!

■ 6. HTTPサービスの有効化

HTTPリクエストを介してHeroServiceがデータを取得できるようにしょう!HTTPを通してデータの保存ができるようになるとな、APIとかのことかね?

HttpClient

HTTPを通してリモートサーバーと通信するための仕組み。app.modual.tsHttpClientModuleをインポートして、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配列にはHttpClientInMemoryWebApiModuleforRoot()をチェーンして、データ取得元となる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=パイプ、複数の機能を一個にまとめられる、便利
  • Observablepipe()で値を加工できる(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周りの知識が乏しい!ゴールデンウイークは料理したりしてる、世界樹早くやりたい、と待ってる時間が一番楽しいカモネ