Shit happens, deal with it!
Error Handling in Angular
When shit happens, Other folks tell you to “Have hope, there is light at the end of the tunnel; even if you can’t see it.” Today you have stubble upon various solutions in dealing with the most important part of the error handling shit in Angular.
Though when it happens you can choose to ignore it and let it bring shame, that’s not my prayers for you, I know it’s complicated but the best way to tackle them depends on where they come from:
External Errors
Are the easiest ones; why (is that) you can always blame someone else like the backend guys or administrator because it is not directly your fault but In this cases, we can’t do much more than handle them, notify the user and pray to the administrator to repair it.
This errors usually come from the server and its properties have a status code starting with 5 (5xx…) and an explanation message, so we are able to know what caused it and react properly. Though, they are some that we can’t handle, like a browser or an OS crash.
Internal Errors
It is all your fault, you need to accept that you have failed to fix this kind of errors and learn the lesson.
What runs through your mind now is how you can differentiate them right? but the good news is that they will notify you when they come.
The Server:
Seems that the properties in the server’s errors are not fixed, at least I haven’t found any standard.
A server error might contain:
- status (or code): Code status starting with 4 (4xx…). There are 28 status.
- name: The name of the error (ie: HttpErrorResponse).
- message: Explanation message (ie: Http failure response for…).
The Client (browser):
Based on a generic Error constructor, Javascript throws an Error each time something goes wrong. Most common errors are ReferenceError (call to a non-existent variable) and TypeError (ie: try to execute a variable, as it was a function). There are 5 more types.
A client error should contain:
- name (ie: ReferenceError).
- message (ie: X is not defined).
And in most modern browsers: fileName, lineNumber and columnNumber where the error happened, and stack (last X functions called before the error).
Errors, Exceptions & CallStack
When Error happens, an exception is thrown at the current point of execution which removes every callstack until the exception is handled by a try/catch block. If the error exceptions are not handled, this will cause the flow of the application stack to break and you will embarrass yourself in front of your users. Damn shit!
How to deal with all this shit
By default, Angular comes with its own ErrorHandler that intercepts all the Errors but if we use that we just want life to control us, let’s control life. Create a new class in your life called: ErrorsHandler
// errors-handler.ts
import { ErrorHandler, Injectable} from '@angular/core';
@Injectable()
export class ErrorsHandler implements ErrorHandler {
handleError(error: Error) {
// Do whatever you like with the error (send it to the server?)
// And log it to the console
console.error('It happens: ', error);
}
}
Don’t be too rude with Angular before it curses you with errors that will confuse you, just tell Angular you have a class you want to use instead of its default, trust me, Angular is your God here it will allow you. (Provide it)
// errors.module.ts
@NgModule({
imports: [ ... ],
declarations: [ ... ],
providers: [
{
provide: ErrorHandler,
useClass: ErrorsHandler,
}
]
}
Now, every time a new shit (error) happens, Angular will log ‘It happens’ followed by the error itself in the console.
How to recognize them
Inside the ErrorHandler, we can check which kind of error it is:
// errors-handler.ts
import { ErrorHandler, Injectable} from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
@Injectable()
export class ErrorsHandler implements ErrorHandler {
handleError(error: Error | HttpErrorResponse) {
if (error instanceof HttpErrorResponse) {
// Server or connection error happened
if (!navigator.onLine) {
// Handle offline error
} else {
// Handle Http Error (error.status === 403, 404...)
}
} else {
// Handle Client Error (Angular Error, ReferenceError...)
}
// Log the error anyway
console.error('It happens: ', error);
}
How to react to each one
Server and connection errors
Server and connection errors affect in the way the app communicates with the server. If the app is not online, the resource doesn’t exist (404), the user is not allowed to access the data (403)… HttpClient will give us an informative Error that we have to handle, but our app won’t crash and the risk of data corruption or lost can be managed.
As a general rule, I think this kind of errors need a notification; a clear message explaining to the user what is happening and how to proceed.
For this purpose, we’ll use a notification service.
// errors-handler.ts
@Injectable()
export class ErrorsHandler implements ErrorHandler {
constructor(
// Because the ErrorHandler is created before the providers, we’ll have to use the Injector to get them.
private injector: Injector,
) { }
handleError(error: Error | HttpErrorResponse) {
const notificationService = this.injector.get(NotificationService);
if (error instanceof HttpErrorResponse) {
// Server or connection error happened
if (!navigator.onLine) {
// Handle offline error
return notificationService.notify('No Internet Connection');
} else {
// Handle Http Error (error.status === 403, 404...)
return notificationService.notify(`${error.status} - ${error.message}`);
}
} else {
// Handle Client Error (Angular Error, ReferenceError...)
}
// Log the error anyway
console.error('It happens: ', error);
}
- Here we could handle specific errors. For example, if it is a 403 error, we could notify the user and then redirect to the login page.
Because the best Error is the one that never happens, we could improve our error handling using an HttpInterceptor that would intercept all the server calls and retry them X times before throwing an Error:
// server-errors.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/retry';
@Injectable()
export class ServerErrorsInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// If the call fails, retry until 5 times before throwing an error
return next.handle(request).retry(5);
}
}
Then we’ll need to tell Angular that he should use our ServerErrorsInterceptor class in every HTTP call:
// errors.module.ts
@NgModule({
imports: [ ... ],
declarations: [ ... ],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: ServerErrorsInterceptor,
multi: true,
},
]
})
Client errors
Client errors seem more dangerous to me. They could completely crash our app, originate corrupt data that could be stored in the server, keep the user working on stuff that wouldn’t be saved…
I think that this situation claims for a more strict response: If something is broken in our app, we need to stop the app, redirect the user to an error screen with all the information, fix it asap and inform to the user that it has been fixed.
For that we could use an ErrorComponent that takes the Error details from the route’s query params:
// errors-handler.ts
@Injectable()
export class ErrorsHandler implements ErrorHandler {
constructor(
private injector: Injector
) { }
handleError(error: Error | HttpErrorResponse) {
const notificationService = this.injector.get(NotificationService);
const router = this.injector.get(Router);
if (error instanceof HttpErrorResponse) {
// Server or connection error happened
if (!navigator.onLine) {
// Handle offline error
return notificationService.notify('No Internet Connection');
} else {
// Handle Http Error (error.status === 403, 404...)
return notificationService.notify(`${error.status} - ${error.message}`);
}
} else {
// Handle Client Error (Angular Error, ReferenceError...)
router.navigate(['/error'], { queryParams: {error: error} });
}
// Log the error anyway
console.error('It happens: ', error);
}
How to keep track of the errors
This is the best way of stopping rubbish in life but African men used to say: “Elders don’t rush talk” let’s keep this discussion till next week stay tuned for Part 2.