search
LoginSignup
18
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Angularエラーハンドリング

先日 ng-japan OnAir を視聴していたところ「エラーハンドリングには ErrorHandler を実装する事が多いと思いますが、Router にもハンドラを渡す事ができるんですよ」なんて会話がありました。

なぬ? えらーはんどらあ??
シラナカッタ....。

という事でエラーハンドリングについて調べた事をメモしておきます。

環境

  • angular 9.1
  • node 13.9

ランタイムのJSエラーをキャッチする

ErrorHandler を継承したクラスを実装します。

// src/app/custom-error-handler.ts

import { ErrorHandler } from '@angular/core';

export class CustomErrorHandler implements ErrorHandler {
  handleError(e) {
    console.warn('👹ErrorHandler👹', e);
  }
}

クラスを AppModule の providers で提供します。

import { ErrorHandler } from '@angular/core';
import { CustomErrorHandler } from './custom-error-handler';

@NgModule({
  declarations: [ AppComponent ],
  imports: [ BrowserModule ],
  providers: [
    {
      provide: ErrorHandler,
      useClass: CustomErrorHandler, // これ
    },
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

ボタンを設置して、わざとランタイムエラーを発生させてみましょう。

// app.component.html
<button (click)="test()">test</button>

// app.component.ts
export class AppComponent implements OnInit {
  test() {
    // TypeError: Cannot read property 'id' of undefined
    return [].find(_ => false).id;
  }
}

カスタムの ErrorHandler がランタイムの JS エラーをキャッチしている事が分かります。

500 ページに飛ばす

エラーをキャッチした後「予期しないエラーが発生しました」ページに飛ばしたい事もあるかもしれません。
その場合はこのように実装すれば良さそうです。

// src/app/custom-error-handler.ts

// コンストラクタにインジェクトするため Injectable を宣言
@Injectable()
export class CustomErrorHandler implements ErrorHandler {
  constructor(
    private router: Router,
    private zone: NgZone,
  ) {}

  handleError(e) {
    console.warn('👹ErrorHandler👹', e);

    this.zone.run(() => {
      // URLに "/500" を表示せず画面遷移する
      this.router.navigate(['/500'], { skipLocationChange: true });
    });
  }
}

HTTP エラーをキャッチする

HttpInterceptor を継承したクラスを実装します。

// src/app/custom-interceptor.ts

import { HttpInterceptor } from '@angular/common/http';

@Injectable()
export class CustomInterceptor implements HttpInterceptor {
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler,
  ): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      catchError((e) => {
        console.warn('👹HttpInterceptor👹', e);

        // 呼び出し元で catchError() できるように例外処理
        return throwError(e);
      }),
    );
  }
}

クラスを AppModule の providers で提供します。

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { CustomInterceptor } from './custom-interceptor.ts';

@NgModule({
  declarations: [ AppComponent ],
  imports: [ BrowserModule, HttpClientModule ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: CustomInterceptor, // これ
      multi: true,
    }
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

ボタンを設置して、わざとエラーを発生させてみましょう。

// app.component.html
<button (click)="test2()">test2</button>

// app.component.ts
export class AppComponent implements OnInit {
  test2() {
    // 存在しない URL へのリクエスト
    this.http.get(
      'http://dummy.restapiexample.com/api/v1/dummy-request',
    ).pipe(
      catchError(() => EMPTY)
    ).subscribe();
  }
}

カスタムの Intercepter が HTTP エラーをキャッチしている事が分かります。

ルーターのエラーをキャッチする

Router の errorHandler を変更します。

// app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
  ) {}

  ngOnInit() {
    this.router.errorHandler = (e) => {
      console.warn('👹router ErrorHandler👹', e);
      this.router.navigate(['/403'], { skipLocationChange: true });
    };
  }

※ errorHandler をどこでセットするのが正しいのか、情報が見つからなかったためとりあえず AppComponent.ngOnInit で宣言しました。

アンカータグを設置して、わざと存在しないページへの遷移を発生させてみましょう。

// app.component.html
<a routerLink="/path/to/invalid">invalid</a>

router.errorHandler が存在しないページへの遷移をキャッチしている事が分かります。

なお、トップページにリダイレクトするようなフォールバックを指定している場合は、ルーターの errorHandler は実行されません。

// app-router.module.ts

const routes: Routes = [
  { ... },
  { // リダイレクトによりエラーが発生しない
    path: '**',
    pathMatch: 'full',
    redirectTo: '',
  }
];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ],
})
export class AppRouterModule {}

おわり

Angular のエラーハンドリングを利用すれば簡単にエラーがキャッチできますね。サンプルのコードでは console.warn しているだけですが、実際の運用ではログ収集サービスにエラー情報を投げるなど、いろいろ有効活用できそうです。

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
What you can do with signing up
18
Help us understand the problem. What are the problem?