🅰️

AngularのHttpClientのエラーハンドリング

2022/05/11に公開

はじめに

今回はAngularにおけるHTTP Clientのエラーハンドリングについてまとめます。
AngularではフレームワークとしてHttpClientクラスが提供されているので利用します。

また、今回のサンプルはstackblitzで公開しています。

https://stackblitz.com/edit/angular-ivy-w4dtzi?file=src/app/data.service.ts

Angularについて

AngularではHTTP Client、ルーティング、フォーム、PWAなどが1st partyとして提供されます。
これらがAngular CLIの強力なng updateコマンドでバージョンアップへ追従できます。

また、日本のミュニティもあり初心者や中級者が次のステップに踏み出せる機会が多い印象です。
ぜひ「何から始めたらいいかわからない」というあなた!
Angularでフロントエンド開発を一通り体験してみてはいかがでしょうか。

https://community.angular.jp/

Angular HttpClientの復習

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

データ型の定義

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

src/app/data.ts
export interface Data {
    key1: string;
    key2: string;
}

サービス

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

src/app/data.service.ts
dataUrl = 'assets/data.json';

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

コンポーネント

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

src/app/app.component.ts
data: Data;

getData() {
  this.dataService.getDataResponse().subscribe({
    next: (response: HttpResponse<Data>) => {
      console.log('response: ', response);
      this.data = response.body;
    },
    error: (e) => {
      switch (e.status) {
        default:
          console.log('error: ', e);
          break;
      }
    },
    complete: () => console.info('complete'),
  });
}

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

angular http response

テンプレート

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

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

Angular HttpClientのエラーハンドリング

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

アプリケーションは、データアクセスが失敗したときに、ユーザーに役立つフィードバックを提供する必要があります。 生のエラーオブジェクトは、フィードバックとして特に役立ちません。 エラーが発生したことを検出することに加えて、エラーの詳細を取得し、それらの詳細を使用してユーザーフレンドリーなレスポンスを作成する必要があります。

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

2種類のエラーが発生する可能性があります。

  • サーバーバックエンドがリクエストを拒否し、404や500などのステータスコードでHTTPレスポンスを返す場合があります。これらはエラーレスポンスです。
  • 要求を正常に完了できないネットワークエラーや、RxJSオペレーター内でスローされた例外など、クライアント側で問題が発生する可能性があります。これらのエラーは0に設定されたstatusと、ProgressEventオブジェクトが含まれるerrorプロパティを持っています。ProgressEventオブジェクトのtypeは詳細な情報を提供する可能性があります。

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

HttpClientは、HttpErrorResponseで両方の種類のエラーをキャプチャします。そのレスポンスを調べて、エラーの原因を特定できます。

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

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

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

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

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

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

angular error response

また、最低限抑えておきたいTimeout処理とRetry処理もrxjsのoperatorを活用することで簡単に実現できそうです。

src/app/data.service.ts
getDataResponse(): Observable<HttpResponse<Data>> {
  return this.http.get<Data>(this.dataUrl, { observe: 'response' })
  .pipe(
    timeout(2500), // タイムアウト処理
    retry(3), // リトライ処理
    catchError(this.handleError),
  );
}

まとめ

AngularにおけるHTTP Clientのエラーハンドリングについてまとめました。
AngularにはInterceptorというロギング/認証処理などの共通処理をまとめる機能があり、コード量が減って良いので次回以降にまとめていきます。

参考URL

https://angular.jp/guide/http#httpによるバックエンドサービスとの通信

https://stackblitz.com/edit/angular-ivy-w4dtzi?file=src/app/data.service.ts

GitHubで編集を提案

Discussion