AngularのHttpClientのエラー制御を理解する

はじめに

Anguler が好きでよく使っています。
小さい簡単な画面であってもAngular Materilaを並べるだけで短時間で実装できますし、中規模程度でもAngular CLI に沿った構成を守ることでスッキリ書けます。

また、他と比較できている訳ではないですがコミュニティも活発で、初心者や中級者が次のステップに踏み出せる機会が多い印象です。
私はフロントエンド開発で生計を立てている訳ではないですが、このご時世UIなしには説得できないことばかりなのでとてもありがたいことです。

今回はSingle Page Application(SPA)に欠かせないHTTPのエラー制御についてまとめます。

Angular HttpClient

日本語ドキュメント で HttpClientを復習します。

まずデータ型をInterfaceとして定義しておきます。

export interface Data {
    key1: string;
    key2: string;
}

GET

このInterfaceをServiceでhttp.getの型パラメーターとして指定します。
observeオプションとして { observe: 'response' } を指定することでheaders含むResponse全体を読むことができます。

dataUrl = 'assets/data.json';

getDataResponse(): Observable<HttpResponse<Data>> {
  return this.http.get<Data>(this.dataUrl, { observe: 'response' });
}

このサービスをComponent側でsubscribeします。
responseにbodyとheadersが入っているのでbodyをデータに格納しています。

data: Data;

getData() {
  this.dataService.getDataResponse()
  .subscribe(
    response => {
      this.data = response.body;
    },
    error => {
      console.log('error: ', error);
    }
  );
}

実際のレスポンスのデータ構造は以下の通りです。

Angular http response
Angular http response data struct

Template側では以下のようにデータをbindすることができます。

key1: {{ data?.key1 }}
key2: {{ data?.key2 }}

エラー制御

私はかれこれComponent側でsubscribeできるerrorでエラーメッセージを出したりログアウト処理を行なったりしていました。
ですが最近、公式ドキュメントを眺めていたところ以下の文章を発見。

データアクセスが失敗したときにユーザーに何らかのフィードバックを与えることは、確かによい考えです。 しかし、HttpClientによって返された生のエラーオブジェクトを表示することは、最善の方法からほど遠いです。

なるほどたしかに。でもResponse statusに応じてメッセージの出し分けならComponent側でもできます。

2種類のエラーが発生する可能性があります。サーバーバックエンドはリクエストを拒否し、HTTPレスポンスに404または500などのステータスコードを返します。 これらはエラーレスポンスです。 あるいは、リクエストが正常に完了するのを妨げるネットワークエラーや、RxJSオペレーターでスローされた例外など、クライアント側で何かが間違っている可能性があります。 これらのエラーは、JavaScriptのErrorEventオブジェクトを生成します。

なるほどたしかに。クライアント側でのエラーはResponseを待ってのハンドリングだけでは必要十分とは言えない気がします。

HttpClientはHttpErrorResponseで両方の種類のエラーを捕捉し、実際に何が起きたのかを調べるためにレスポンスを調べることができます。 エラー検査、解釈、および解決は、componentではなくserviceで実行したいことです。

なるほどたしかに。というわけでService側のでエラーハンドリングを調査してみます。

Serviceでのエラーハンドリング

公式ドキュメントではServiceとしてhandleErrorを定義することを提案しているようです。

getDataResponse(): Observable<HttpResponse<Data>> {
  return this.http.get<Data>(this.dataUrl, { observe: 'response' })
  .pipe(
    catchError(this.handleError),
  );
}

private handleError(error: HttpErrorResponse) {
  // クライアント側あるいはネットワークによるエラー
  if (error.error instanceof ErrorEvent) {
    console.error('An error occurred:', error.error.message);
  // サーバー側からのエラー
  } else {
    console.error(
      `Backend returned code ${error.status}, ` +
      `body was: ${error.error}`);
  }
  // エラーメッセージの返却
  return throwError(
    'Something bad happened; please try again later.'
    );
}

試しに誤ったURLを入力してみるとサーバーサイドのエラーとしてstatusとerror内容を取得し、Component側へも任意のエラーメッセージを返却できていることがわかります。

Angular error message
Angular error message

また、最低限抑えておきたいTimeout処理Retry処理もrxjsのoperatorを活用することで簡単に実現できそうです。
rxjsのドキュメントもStackBlitzによる動かすことのできるサンプルが多いのでとてもわかりやすいです。

getDataResponse(): Observable<HttpResponse<Data>> {
  return this.http.get<Data>(this.dataUrl, { observe: 'response' })
  .pipe(
    timeout(2500),
    catchError(this.handleError),
  );
}

デモアプリ

今回作ったものは以下に置いてあります。

github.com

まとめ

AngularのHttpClientのエラー制御についてまとめました。
手を動かして動作するものを作ると理解が深まります。
Angularでもう少し調べたいのはグラフライブラリー周りなので、Googlechart、Chart.js、plotly.jsあたりを調査してみます。