Console log error handled by custom handler chain

Angular error handlers + remote logging

In this tutorial we will be creating an Angular Error handler that will log errors to your server, in a way that allows you to register multiple different handlers like if they were Interceptors!

I’ve come across the need of logging to some server many times, but it turns out that the available libraries are outdated, complex to setup or just heavy for the task I want to achieve.

Today, I will show you how to create a very simple, yet powerful, way of registering multiple global angular error handlers on your app.

The problem – Only 1 (global) Angular Error Handler allowed

We want to have one handler to log on the console and another one to log on a remote server.

The first thing you may know is that Angular already comes with a global Error Handler. You can find it’s documentation at the Angular docs

So the first that comes to your mind is to replace It by your own handler, right?

We can do it like so:

@NgModule({
  providers: [
    {provide: ErrorHandler, useClass: MyCustomHandler}
  ]
});

The wrong (but common) approach – Extension/Inheritance

The issue?

We are replacing the default Angular Error Handler, which means that It won’t log errors to console anymore unless we instruct our custom handler to do so.

The fix?

Extending the default ErrorHandler…? Right…? —– > NO!

You can do it, but this will not scale. You will need to call super in order to get the parent’s behavior working, and what happens if we want to have 3, 4 or 5 handlers, each with a different way of handling things without loosing the others to work…?

We would end up having a long chain of inherited children tightly coupled to each other.

The right solution – Multi Injectors

Our handlers will have different behaviors so extension makes no sense here.

What we want is a way to instantiate our handlers independently, and to have all of them catch the errors in a similar fashion to how Angular HttpInterceptors work.

So we want to register them like this:

@NgModule({
  providers: [
    {
      provide: ErrorHandler,
      useExisting: RemoteErrorHandler,
      multi: true,
    },
    {
      provide: ErrorHandler,
      useClass: ErrorHandler,
      multi: true,
    },
  ]
});

NOTE: This won’t work, because the default ErrorHandler only supports one instance to be used.

What we will do to achieve that is to create a “Handler manager”, which is going to be simple handler that implements the ErrorHandler interface, and inside of it we will get all the other handlers that we will have injected in the application by using a custom InjectionToken

So the working approach will be the following:

@NgModule({
  providers: [
    {
      provide: ERROR_HANDLERS,
      useExisting: RemoteErrorHandler,
      multi: true,
    },
    {
      provide: ERROR_HANDLERS,
      useClass: ErrorHandler,
      multi: true,
    },
    {
      provide: ErrorHandler,
      useExisting: ErrorHandlerManager,
    }
  ]
});

What it does

In lines 14 & 15 we are registering our custom Error handler manager as the Angular global error handler.

Then, from lines 3 to 12, we are registering all our other handlers, with the “Multi” property and under the token “ERROR_HANDLERS”

How does it work

It works because we created a custom Injection Token called “ERROR_HANLDERS”, and we are using it from within our Handler Manager.

export const ERROR_HANDLERS = new InjectionToken<ErrorHandler[]>('ERROR_HANDLERS');

@Injectable({providedIn: 'root'})
export class ErrorHandlerManager implements ErrorHandler {
  constructor(private injector: Injector){}

  handleError(error: any): void {
    const handlers = this.injector.get(ERROR_HANDLERS, []);
    handlers.forEach(handle => handle.handleError(error));
  }
    
}

What we have here:

  • A Injection Token to be able to instantiate and retrieve our handlers.
  • A Manager class that gets triggered when an error occurs, and that iterates over all our handler instances to execute their own handleError() method.

Logging to remote server – The easy way on your Angular Error Handlers!

Once we have setup our error handler injectiors, we can just use one of those services to log to our custom server.

Using the HttpClient is the easiest way:

@Injectable({providedIn: 'root'})
export class RemoteErrorHandler implements ErrorHandler {
  constructor(private http: HttpClient) {}

  handleError(error: any) {
    this.http.post('https://my-custom-url.com', {
      error,
      time: new Date()
    });
  }
}

Feel free to set whatever implementation you may like!

Final thoughts

With this solution we get an extensible way of adding as many error handlers as we want while keeping scalability and maintainability in our application.

Angular provides very powerful tools but sometimes it is not so clear how to get the most out of them.

Last words

ENJOY!

And comment if you liked it!

Suggestions, improvements and questions are welcome!