Angular has built-in mechanisms for dealing with unhandled exceptions. In an application, we only need to provide this handler when we want to customize the display and handling of errors.
Our goal is to have a nice customized display to the user like so.
Diving right in, within our @NgbModule definition, we define the handler we want Angular to provide/use for errors:
{ provide: ErrorHandler, useClass: AppErrorHandler },
The basic ErrorHandler class has only a single method that we must implment: “handlerError.” This makes things pretty easy. My basic AppErrorHandler looks like this:
import { Injectable, Injector, Inject, ErrorHandler, ChangeDetectorRef, NgZone } from "@angular/core"; import { DialogService, NotificationService } from './services/index'; import { Observable } from "rxjs/Observable"; import { NgbModalOptions } from "@ng-bootstrap/ng-bootstrap"; @Injectable() export class AppErrorHandler extends ErrorHandler { private notificationService: NotificationService; private dialogService: DialogService; constructor(private injector: Injector) { super(); }
You’ll notice references to a DialogService and NotificationService. You can read up on how those work, but it’s not really important to implementing the ErrorHandler. I’m also injecting an Injector (that sounds weird) to avoid circular references which can occur when injecting certain services at setup time.
The parts that I was interested in customizing when handling an error were the messages displayed to the user and how they are displayed. Most examples I have seen use plain JavaScript confirm dialogs, but I wanted some nice Bootstrap dialogs and such.
Initially, we have to get a reference to the DialogService and NotificationService.
handleError(error: any) { try { if (this.notificationService == null) { this.notificationService = this.injector.get(NotificationService); } if (this.dialogService == null) { this.dialogService = this.injector.get(DialogService); }
Using the NotificationService, we’ll display a growl/toast to the user.
this.notificationService.warning("An unresolved error has occurred. Please reload the page to correct this error");
And the big chunk of code below displays a dialog to the user using the DialogService. You can see from my comment that this was interesting. Unless I used “zone.run(() => {..},” the dialog would not display until a DOM event, like a window click, which seemed really odd to me. The DialogService itself forces a component change check which, generally, lets it function without issue when being called outside of a UI context/scope. Anyway, it is a point worth making.
// For some reason, unless I run the zone, the dialog doesn't get display unless a DOM event is triggered.. var zone: NgZone = this.injector.get(NgZone); zone.run(() => { console.log('Running zone..'); console.log("Showing confirm dialog."); let options: NgbModalOptions = { size: 'lg' }; var modalPromise: Promise<any> = this.dialogService.open("Fatal Error!", "An unresolved error has occured. Do you want to reload the page to correct this?<br/><br/>Error: " + error.message, true, "Yes", "No", options); var newObservable: Observable<any> = Observable.fromPromise(modalPromise); newObservable.subscribe( (res) => { if (res === true) { window.location.reload(true); super.handleError(error); } else { super.handleError(error); } }, (reason) => { console.log("Dismissed " + reason); super.handleError(error); } ); super.handleError(error); }); } catch (e) { // if, for some reason, we can't display the dialog, fall back to plain confirm if (confirm("Fatal Error!\nAn unresolved error has occured. Do you want to reload the page to correct this?\n\nError: " + error.message)) window.location.reload(true); super.handleError(error); }
All of the code is wrapped in a try/catch so that if our application is broken to the point that not even the services can be resolved, we’ll still get a JavaScript confirm dialog displayed to the user. I found creating an ErrorHandler that utilized other components and services within the application to provide more insight into the inner workings of Angular.