Error Handling in Angular
In Previous topic we have looked into configuration part and saw the use of APP_INITIALIZER. In this topic we will cover error handling in angular application.
Error Handling is an important part of designing an angular application, if anything goes wrong javascript will throw errors. For Example:
- Say we call a variable and it doesn't exist.
- when a value is not of the expected type.
- or, some internal error in javascript.
Apart from these some errors like null pointer exception, session expiration, or unauthorized user etc..
There are two types of error handling mechanism is angular:
- Server side errors.
- Client side errors.
Server-Side-Errors ➤➤
Server-side errors or HTTP errors are throws when you send a HTTP request through HttpClient. So what happen here when ever there is a error HttpClient captures the error and wraps it in the generic HttpErrorResponse before passing to application. The error property of HttpErrorResponse contains error object.
Catch Http Errors ➤➤
We can catch the Http errors at three different places on client side.
- service : HttpClient
- Globally : HTTP Interceptor
- component : Observable
1. Component Level ➤➤
We created a method in user service which will return student specific notification. The following is the getNotification() method from the service.
getNotification(userType): Observable<any> {
return this.httpClient.get(EnvironmentConstants.getNotification, {params:
new HttpParams().set('userRole', userType)
});
}
EnvironmentConstants.getNotification = API URL = "http://localhost:8080/getNotification"
We subscribe to the httpclient.get method in our component class.
getNotification() {
const response: Observable<any> = this.userService.getNotification(
window.history.state.rowDetail.roles);
response.subscribe({
next: data => {
if (data && data['status'].toLowerCase() === 'success') {
this.notificationData = data['data'];
}
},
error: err => {
console.log("Error");
this.errorMessage = err;
console.log("Error During fetching notification details !")
}
});
}
The subscribe method has three callback arguments success , error and completed.
The observable invokes the first callback success when http request successfully return a response. The third callback completed is called when the observable finishes without any error.
The second callback error is invoked when the http request end in Error. We handle error by figuring out the type of error and handle it accordingly. The type of error object is HttpErrorResonse.
error: err => {
console.log("Error");
this.errorMessage = err;
console.log("Error During fetching notification details !")
}
2. Service Level ➤➤
We can also catch errors in the service by using catchError Operator. After handling the error you can re-throw it to the component for further handling.
getNotification(userType): Observable<any> {
return this.httpClient.get(EnvironmentConstants.getNotification,
{params: new HttpParams().set('userRole', userType)
}).pipe(
catchError((err) => {
console.log('error caught in service')
console.error(err);
//Handle the error here
return throwError(() => (new Error(err))); //Rethrow it back to component
})
);
}
3. Globally Exception Handler ⮞ HttpInterceptor
Another way to handle Angular Error by the use of HttpInterceptor. The Interceptor catches Http requests and other responses before passing them for next stage. Some of the common error to every HTTP request are :
- Unauthorized Access.
- Invalid API endpoints.
- Server Down.
- Network Error. etc.
Sample code below:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
withCredentials: true,
});
return next.handle(req).pipe(catchError((err : HttpErrorResponse)=>{
console.log('error is intercept')
console.error(err);
this.handleServerSideError(err);
return throwError(() => (new Error(this.handlerResponse)));
}));
}
handleServerSideError(error: HttpErrorResponse) {
switch (error.status) {
case 400: // means the request could not be understood by the server.
this.handlerResponse = "Bad Request, please try again later .";
break;
case 401: // means lacks valid authentication credentials for the target resource.
this.handlerResponse = "Unauthorized, please try again later.";
break;
case 403: // means you are not allowed access to the target resource.
this.handlerResponse = "Forbidden access is denied";
break;
case 500: // means there's an issue or temporary glitch with the app programming
this.handlerResponse = "Internal server error, please try again later.";
break;
}
}
we catch error using catchError RxJS operator. we then re-throw it to subscriber using the throwError.
The catchError is added to the request pipeline using RxJS pipe operator. When the error occurs in the HTTP Request it is intercepted and invokes the catchError. Inside the catchError you can handle the error and then use throwError to throw it to the service.
Client-side-Error ➤➤
Client side errors relates to all errors that occurs as a result of a code unit. It demonstrate that there is a flaw in code. There are two ways to handle these errors:
- Default error handling
- Global error handling
Types of these error are: EvalError, InternalError, RangeError, ReferenceError, SyntaxError, URIError, TypeError. So any error that are induced by developers comes under client error category.
Whenever such errors occurs, execution of JavaScript stops and the screen freezes giving user a bad experience. So best practice is to handle such errors and perform a relevant action like routing to error page and display then some custom message.
Angular comes up with class ErrorHandler that provides default method handleError(error : Error) which we can use to catch Error.
@Injectable()
class MyErrorHandler implements ErrorHandler {
handleError(error: Error) {
// do something with the exception like
//router.navigate(['/error-page']);
}
}
We can use handleError(error : Error) to catch the error and redirect users to a generic error-page. One catch is there how do we inject the helper service in our custom ErrorHandler implementation?
If we inject the service as we usually do
constructor(private router: Router){}
This will throw error as show below
This is because Angular creates ErrorHandler before providers otherwise it wont be able to catch errors that occurs in early phase of application. Hence the provider will not be available to ErrorHandler So we need to inject our services using injectors.
@Injectable()
class MyErrorHandler implements ErrorHandler {
constructor(private injector: Injector){}
handleError(error: Error) {
const router = this.injector.get(Router);
router.navigate(['/error-page']);
}
}
By this way one problem will be solved but leads to another problem.
"Navigation triggered outside Angular zone, did you forgot to call ngZone.run()"
Here the problem is ErrorHandler runs outside the regular ngZone, So the navigation should take place outside of the Zone so that regular flow of change detection is not hampered.
@Injectable()
class MyErrorHandler implements ErrorHandler {
constructor(
private injector: Injector,
private zone: NgZone,
){}
handleError(error: Error) {
const router = this.injector.get(Router);
this.zone.run(() => router.navigate(['/error-page']));
console.error('Error Caught: ', error);
}
}
Once we had achieved this we need to provide this service to root module ex: appModule.
@NgModule({
providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
})
class MyModule {}
Comments
Post a Comment