はじめに
今回は、外部APIからデータを取得して表示する際のエラーハンドリングについて考えてみます。
方法1
ERROR HANDLING WITH ANGULAR`S ASYNC PIPE でも紹介されている方法です。
エラーを通知する先としてSubjectを用意してそれをビューテンプレートで表示します。
Service
テスト用のモックを作成します。(実際に外部APIと通信は行いません。)
get(id: number): Observable<User> {
return new Observable((subscriber) => {
setTimeout(() => {
if (!id) {
subscriber.error('id is required');
return
}
subscriber.next(new User(id, "hello"))
subscriber.complete();
}, 1000);
})
}
Component
user$: Observable<User> | null = null;
error$ = new Subject<Error>();
get(id: number) {
this.error$.next()
this.user$ = this.userService.get(id).pipe(
catchError(err => {
this.error$.next(err);
return throwError(err);
})
);
}
View
asyncでuserオブジェクトを取得できない場合は、ローディング中もしくは、エラーが発生している可能性があります。
その場合は loadingOrErrorTemp を表示して、その中で、エラーが出ている場合とローディング中の場合を、error$
の値を参照して判定します。
<ng-container *ngIf="user$">
<div *ngIf="user$ | async as user; else loadingOrErrorTemp">{{user | json}}</div>
</ng-container>
<ng-template #loadingOrErrorTemp>
<div *ngIf="error$ | async as err; else loadingTemp">{{err}}</div>
<ng-template #loadingTemp>loading</ng-template>
</ng-template>
方法2
次の方法は、Go言語のエラーハンドリングからヒントを得た方法です。
Goでは、以下のように、メソッドの戻り値として、エラーを受け取りハンドリングを行います。
value, err := get(id)
if err != nil {
log.Println(err)
return
}
これをAngularのエラーハンドリングに適応すると以下のようになります。
Object
userオブジェクトをラップして、エラーの値もサービスから返すようにします。
export class UserResponse {
user?: User;
errorMsg?: string;
}
Service
userResponseオブジェクトを返すサービスを作成します。
getUserResponse(id: number): Observable<UserResponse> {
return new Observable((subscriber) => {
setTimeout(() => {
const userResponse = new UserResponse();
if (!id) {
const errMsg = 'id is required';
userResponse.errorMsg = errMsg
subscriber.next(userResponse);
subscriber.complete();
return
}
userResponse.user = new User(id, "hello");
subscriber.next(userResponse);
subscriber.complete();
}, 1000);
})
}
Component
getUserResponse(id: number) :Observable<UserResponse>{
this.userResponse$ = this.userService.getUserResponse(id);
}
Template
前回の方法と異なり、asyncで値を取得できていない状態はローディングの時のみとなります。
<ng-container *ngIf="userResponse$">
<div *ngIf="userResponse$ | async as userResponse; else loadingTemp">
<ng-container *ngIf="userResponse.errorMsg as err">{{err}}</ng-container>
<ng-container *ngIf="userResponse.user as user">{{user?.id}}</ng-container>
</div>
<ng-template #loadingTemp>loading</ng-template>
</ng-container>
まとめ
どちらの方法が良いかは好みかと思いますが、個人的には方法2の方が直感的でコードの見通しも良いのではと思いました。