By default, when errors occur in Angular during runtime, they are written to the browser console. This is great during development and testing but not great when running on production. This is one environment where you have no access to. And the users may lack technical skills to help. No matter how hard you work on your app, errors are almost inevitable.
It is therefore necessary to be able to handle errors in a friendly manner and collect them for investigation, analysis and correction. In this post, I will show you how to intercept the default error handler, and then send the errors to a remote logging service (Simple REST API call) and show a friendly message to the user.
Why?
In a previous post, I wrote about catching and logging http errors and some readers asked whether it is wise to trust logs from the users. My argument is simple, while that data is not 100% trustworthy, it provides an important diagnostic data which can guide you in discovering new errors in your application.
It helps you know which parts of your application require attention and investigation. Most of your users will most likely leave your app without leaving feedback if it ever fails. Also, collecting such errors provides technical information (i.e. Stack Trace) which is not possible to get from users. You could also use Machine Learning to dig into the logged data and wind out false reports.
Creating a Custom Error Handler Class
First, we need to create a new class using Angular CLI.
ng generate class custom-error-interceptor
Next, we need to implement the ErrorHandler interface for our new class.
export class CustomErrorHandler implements ErrorHandler {}
Then, we need to add an handleError method in our class, which will catch the errors in our app and do something with the errors.
handleError(error: any) {}
The method accepts an error parameter which is the error content. Now, we need to do something with our error when it gets here. One option would be sending it to a remote logging server such as AWS Cloud Watch and provide a custom error message to the user. To do this, we need to create a logger service and inject it to our custom error handler class.
constructor(private loggerService: LoggerService) {}
And then inside our handleError method, we can send the error to an API End Point.
this.loggerService.log(error).subscribe();
After that we can show a friendly message to the user. For this we will use Angular Material Snackbar. First, we are going to inject MatSnackBar to our custom error handler class, next to our loggerService we injected previously.
constructor(private loggerService: LoggerService,private snackBar: MatSnackBar) {}
Now, we can show our message:
this.snackBar.open('Your message here', 'Dismiss', {
duration: 2000
});
NB: This can be replaced with something like Angular Material Dialog or Bootstrap Modal Window. You can also use Angular Flex Layout to adaptively show either a snack bar on a small device and a modal window on a large device.
Now, our complete class should look like this now:
export class CustomErrorHandler implements ErrorHandler {
constructor(
private loggerService: LoggerService,
private snackBar: MatSnackBar
) {}
handleError(error: any) {
// send to the observable
this.loggerService.log(error).subscribe();
// next show a friendly message
this.snackBar.open('Your message here', 'Dismiss', {
duration: 2000
});
}
}
Our Logger Service
Our logger service will look something like this:
@Injectable({
providedIn: 'root'
})
export class LoggerService {
constructor(private http: HttpClient) {}
log(error: any): Observable<boolean> {
return this.http
.post(
'Your URL Here',
error, // <<<<=============== sending error as body
{
observe: 'response'
}
)
.pipe(map(e => true));
}
}
NB: Please note, that am sending the error as the body. This might not work depending on the logging server. You might have to parse the error and compose a message before sending it to the logging server. You can also send it a base64 string and decode it on the server before storing it.
Registering the Custom Error Handler
Finally, for our custom Error Handler to work, we need to add it to the list of our providers:
@NgModule({
declarations: [AppComponent, HttpClientModule],
imports: [BrowserModule, BrowserAnimationsModule],
providers: [
{
provide: ErrorHandler,
useClass: CustomErrorHandler
}
],
bootstrap: [AppComponent]
})
export class AppModule {}