5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Angularで複数のAPIを呼び出す方法とエラー処理まとめ

Posted at

はじめに

Angularで、1回の画面表示で複数のAPIを呼び出したい時がそれなりにある。
また、エラー処理に関する情報が少ない気がするので、エラー処理も含めて、どのようにするのがよいのか整理してみた。
Angularのバージョンは6.1.0(RxJS 6)。

前提

呼び出すAPIは次の形式とする。

xxx.service.ts
export class XxxService {
  api1(): Observable<Entity1>;
  api2(key: string): Observable<Entity2>;
}

api1 を呼び出した後、 api2 を呼び出す想定。

また、API呼び出し中の画面は全画面ブロックではなく、部分的にローディング中メッセージ(もしくはアイコン)を表示する想定。

2つのAPIが独立な場合

もし api1 の戻り値を使用せず api2 を呼び出せるのであれば、単純に別々に呼び出せばよい。

xxx.component.ts
@Component({ /* 省略 */ })
export class XxxComponent implements OnInit {
  result1: Observable<Entity1 | { error: any }>;
  result2: Observable<Entity2 | { error: any }>;

  constructor(private xxxService: XxxService) { }

  ngOnInit() {
    this.result1 = this.xxxService.api1().pipe(
      catchError(e => of({ error: e }))
    );
    this.result2 = this.xxxService.api2('key2').pipe(
      catchError(e => of({ error: e }))
    );
  }
}

上記のように catchError でエラー内容を Observable に変換しておくと、画面側で「正常」「エラー」「読み込み中」の表示切替が可能になる。

xxx.component.html
<ng-container *ngIf="result1 | async; let result1; else loading">
  <ng-container *ngIf="!result1.error else result1Error">
    {{result1}}
  <ng-container>
  <ng-template #result1Error>
    result1の取得に失敗しました。詳細:{{result1.error}}
  </ng-template>
</ng-container>
<ng-container *ngIf="result2 | async; let result2; else loading">
  <ng-container *ngIf="!result2.error else result2Error">
    {{result2}}
  <ng-container>
  <ng-template #result2Error>
    result2の取得に失敗しました。詳細:{{result2.error}}
  </ng-template>
</ng-container>
<ng-template #loading>
  読み込み中
</ng-template>

2つのAPIが同期的動作を行う場合

api1 の呼び出し後、その結果を用いて api2 を呼び出す必要がある場合、 pipe でつなげて処理する。

画面表示に2つ目のAPIの結果のみが必要な場合

flatMap を用いて、 api2 の結果を取得する。

xxx.component.ts
@Component({ /* 省略 */ })
export class XxxComponent implements OnInit {
  result2: Observable<Entity2 | { error: any }>;

  constructor(private xxxService: XxxService) { }

  ngOnInit() {
    this.result2 = this.xxxService.api1().pipe(
      flatMap(result1 => this.xxxService.api2(result1.key)),
      // api1とapi2のエラー処理
      catchError(e => of({ error: e }))
    );
  }
}

api1 で発生したエラーも api2 で発生したエラーも、1つの catchError で処理することになるため、エラーの形式は揃えておく必要がある(通常は規約で揃っているはず)。

もしエラー処理を別々に行いたい場合は、いったん subscribe する。

xxx.component.ts
@Component({ /* 省略 */ })
export class XxxComponent implements OnInit {
  result2: Observable<Entity2 | { error: any }>;

  constructor(private xxxService: XxxService) { }

  ngOnInit() {
    this.xxxService.api1().subscribe(
      result1 => {
        this.result2 = this.xxxService.api2(result1.key).pipe(
          // api2のエラー処理
          catchError(e => of({ error: e }))
        );
      },
      // api1のエラー処理
      e => console.error(e)
    );
  }
}

2つのAPIの結果を画面側で表示する場合

変数の型をObservableにしない場合

先の例で、 subscribe で得られる result1 を変数に入れればよい。
api1api2 のエラーを個別に処理したい場合も、この方法での実装になる。

xxx.component.ts
@Component({ /* 省略 */ })
export class XxxComponent implements OnInit {
  result1: Entity1;
  result2: Observable<Entity2 | { error: any }>;

  constructor(private xxxService: XxxService) { }

  ngOnInit() {
    this.xxxService.api1().subscribe(
      result1 => {
        this.result1 = result1;
        this.result2 = this.xxxService.api2(result1.key).pipe(
          // api2のエラー処理
          catchError(e => of({ error: e }))
        );
      },
      // api1のエラー処理
      e => console.error(e)
    );
  }
}

2つの結果をObservableで受け取る場合

forkJoin を用いて結合する。エラー処理が共通化できるのであれば、コードはすっきりする。
もちろん、 api1 でエラーが発生した場合は、 api2 は実行されない。

xxx.component.ts
@Component({ /* 省略 */ })
export class XxxComponent implements OnInit {
  result: Observable<any>;

  constructor(private xxxService: XxxService) { }

  ngOnInit() {
    this.result = this.xxxService.api1().pipe(
      flatMap(result1 => forkJoin(of(result1), this.xxxService.api2(result1.key))),
      // api1とapi2のエラー処理
      catchError(e => of({ error: e }))
    );
  }
}

ただ、上記のように実装すると、結果は配列で取得されるため、テンプレート側ではインデックスで参照することになる。

xxx.component.html
<ng-container *ngIf="result | async; let result; else loading">
  <ng-container *ngIf="!result.error else resultError">
    {{result[0]}}<br>
    {{result[1]}}
  <ng-container>
  <ng-template #resultError>
    取得に失敗しました。詳細:{{result.error}}
  </ng-template>
</ng-container>
<ng-template #loading>
  読み込み中
</ng-template>

終わりに

全画面ローディングを入れずにエラー処理も入れるとなると、一工夫必要。
もっと良いやり方があるかもしれないため、ぜひ情報をいただければ。

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?