Exception Handling with Angular (6.0.7)

Our target : globally catch errors.

The angular way to catch error globally is to implement a custom ErrorHandler.

Basically, I would like to act differently when we have server or client errors.

When the server notifies an error, Angular throws an HttpErrorResponse. For client-side error we will have an Error instance instead.

When the user is not authorized (401), he may be redirected to the login page.
When the user is not allowed to see a resource (403), he may be redirected to the forbidden page.

1. The errors service

import {Injectable, Injector} from '@angular/core';
import {LocationStrategy, PathLocationStrategy} from '@angular/common';
import {HttpClient, HttpHeaders, HttpErrorResponse} from '@angular/common/http';
import {Router, Event, NavigationError} from '@angular/router';
import {Observable, of} from 'rxjs';
// library to deal with errors: https://www.stacktracejs.com
import * as StackTraceParser from 'error-stack-parser';
import {ErrorDetail} from '../models/error';
import {ApiResponse} from '../models/api-response';
/**
*
* The Errors service.
*
* @class ErrorsService
*
*/
@Injectable()
export class ErrorsService {
/**
* @constructor
*
* @param injector - The injector service
* @param router - The router service
*
*/
constructor(
private injector: Injector,
private router: Router
) {
// Subscribe to the NavigationError
this.router
.events
.subscribe((event: Event) => {
if (event instanceof NavigationError) {
// Redirect to the ErrorComponent
this.log(event.error)
.subscribe(value => {
this.router.navigate(['/error'], {queryParams: {msg: 'A navigation error happened.'}});
});
}
});
}
/**
* Log error to console and send error to server
*
* @param error - The client | server errors
*/
log(error: Error | HttpErrorResponse): Observable<ApiResponse> {
// Log the error to the console
console.error(error);
let errorDetail = this.addContextInfo(error);
//post to server
return this.sendError(errorDetail);
}
/**
* Create an error context information
*/
private addContextInfo(error: Error | HttpErrorResponse): ErrorDetail {
let errorDetail = new ErrorDetail();
errorDetail.appId = 'vmcp';
errorDetail.time = new Date().getTime();
errorDetail.user = 'User';
if (error) {
errorDetail.name = error.name || null;
errorDetail.message = error.message;
errorDetail.url = this.getPath();
if (error instanceof HttpErrorResponse) {
errorDetail.status = error.status;
errorDetail.stack = null;
} else {
errorDetail.status = 1000;
errorDetail.stack = StackTraceParser.parse(error);
}
}
return errorDetail;
}
/**
* Get path
*/
private getPath(): string {
const location = this.injector.get(LocationStrategy);
return location instanceof PathLocationStrategy ? location.path() : '';
}
/**
* Send error
*
* @param errorDetail - The error detail to be sent
*/
private sendError(errorDetail: ErrorDetail): Observable<ApiResponse> {
const http = this.injector.get(HttpClient);
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
};
console.log('Error sent to the server: ', errorDetail);
return http.post<ApiResponse>('api/rest/admin/errors', errorDetail, httpOptions);
}
}


Basically, this service takes all errors information and send them to an endpoint (e.g. Help Desk).


2. The errors handler

import {ErrorHandler, Injectable, Injector, NgZone} from '@angular/core';
import {HttpErrorResponse} from '@angular/common/http';
import {Router} from '@angular/router';
import {ErrorsService} from '../services/errors.service';
/**
*
* Global errors handler.
*
* @class ErrorsHandler
* @implements ErrorHandler
*
*/
@Injectable()
export class ErrorsHandler implements ErrorHandler {
private serverErrorMsg: string = 'We\'re sorry! The server has encountered an internal error and was unable to complete your request.';
private clientErrorMsg: string = 'We encountered an error and could use your help. Please contact us and tell us what you were doing when this error occured.';
/**
* @constructor
*
* @param injector - We use the injector service because
* this error handler is instantiated before the providers.
* @param zone - The NgZone
*/
constructor(
private injector: Injector,
private zone: NgZone
) {}
/**
* Handle error method
*
* @param error - The errors
*/
handleError(error: Error | HttpErrorResponse) {
//http errors
if (error instanceof HttpErrorResponse) {
if (!navigator.onLine) {
// No Internet connection
this.redirectToErrorPage('No Internet Connection.');
} else {
if (error.status === 400) {
//business errors
this.redirectToErrorPage(error.message);
} else {
this.notifyHelpDesk(error, this.serverErrorMsg);
}
}
} else {
// Client Error Happend
this.notifyHelpDesk(error, this.clientErrorMsg);
}
}
private notifyHelpDesk(error: Error | HttpErrorResponse, msg: string) {
const errorsService = this.injector.get(ErrorsService);
errorsService.log(error).subscribe(value => {
this.redirectToErrorPage(msg);
});
}
private redirectToErrorPage(msg: string) {
const router = this.injector.get(Router);
//Router navigation should only be triggered inside Angular zone
this.zone.run(() => {
router.navigate(['/error'], {queryParams: {msg: msg}});
});
}
}


The router navigation should only be triggered inside Angular zone.

3. The core routing module

import { NgModule, ErrorHandler } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ErrorsService } from './services/errors.service';
import { ErrorsHandler } from './handlers/errors.handler';
import { ContactService } from './services/contact.service';
import { ErrorComponent } from './components/error.component';
import { EntryComponent } from './components/entry.component';
const routes: Routes = [
{
path: 'error',
component: ErrorComponent
}
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class CoreRoutingModule { }
export const CORE_COMPONENTS_EXPORTS = [
EntryComponent
];
export const CORE_COMPONENTS = [
ErrorComponent,
EntryComponent
];
export const CORE_PROVIDERS: any[] = [
ErrorsService,
{
provide: ErrorHandler,
useClass: ErrorsHandler,
},
ContactService
]


Please take a look at my demo.

Don't hesitate to share and comment...

Comments

Popular posts from this blog

Spring JPA : Using Specification with Projection

Chip input using Reactive Form