Global Error Handling with Angular2+

Error handling is one of those things no one wants to deal with, well in Angular you can create a global handler and never have to worry again.

Angular defines an ErrorHandler class that will allow us to override it and handle custom logic. Typically, I have my error handlers do things like:

  • Show some sort of notification to the user via a dialog or growl-like component (ideally with a emoji)
  • Send the error details to the server for logging

Whatever you're doing in this logic, here’s how we go about it…

First, let's define a GlobalErrorHandler class that will inherit from ErrorHandler like so:

import { ErrorHandler, Injectable} from '@angular/core';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
  constructor() { }
  handleError(error) {
console.log('Hio')
     // IMPORTANT: Rethrow the error otherwise it gets swallowed
throw error;
}

}

Now in our module, we have to tell Angular to use our GlobalErrorHandler instead of the default one like:

import { NgModule, ApplicationRef, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { GlobalErrorHandler } from './error-handler';
import { ServicesModule } from './services';

import { AppComponent } from './app.component';

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

Now we if we just do throw new Error('Im errorn') anywhere in our application, we will see our cool console message.

There is one problem though, since error handling is really important it needs to be loaded first, thus making it not possible to use dependency injection in the constructor to get other services such as the error handle api service to send the server our error details. As a result, we have to manually call the injector with the service name in the execution of the handleError function like:

import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { LoggingService } from '../services';
import * as StackTrace from 'stacktrace-js';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error) {
const loggingService = this.injector.get(LoggingService);
const message = error.message ? error.message : error.toString();
    // log on the server
loggingService.log({ message });
});
    throw error;
}

}

That one stumped me for a bit, but to wrap things up I like to use StackTrace.js to get the full stack and also add the current URL to the message going upstream to the server, so the final implementation looks something like:

import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { LocationStrategy, PathLocationStrategy } from '@angular/common';
import { LoggingService } from '../services';
import * as StackTrace from 'stacktrace-js';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error) {
const loggingService = this.injector.get(LoggingService);
    const location = this.injector.get(LocationStrategy);
    const message = error.message ? error.message : error.toString();
    const url = location instanceof PathLocationStrategy
? location.path() : '';
   // get the stack trace, lets grab the last 10 stacks only
StackTrace.fromError(error).then(stackframes => {
const stackString = stackframes
.splice(0, 20)
.map(function(sf) {
return sf.toString();
}).join('\n');
    // log on the server
loggingService.log({ message, url, stack: stackString });
});
throw error;
}

}

I’m #ngPanda

I hope you enjoyed the post, if you liked it follow me on Twitter and Github for more JavaScript tips/opinions/projects/articles/etc!