I'm trying to implement a custom ExceptionHandler
in an Angular 2 app which submits uncaught errors to a custom AlertsService
. The goal is to allow the main App
component to subscribe to the alerts provided by the AlertsService
so that it can display the errors in the UI.
The problem I'm seeing is that errors submitted to the AlertsService
by the custom ExceptionHandler
are not reflected in the UI until another error is encountered. This causes the UI to always be one alert behind what is actually being provided by the AlertsService
.
My guess is that this behavior has something to do with change detection and the special case of the ExceptionHandler, but I'm not sure where to go from here. Looking to the Angular2 experts for help!
Sample code below, plunk here:
import { Component, ExceptionHandler, Injectable, OnInit, provide } from '@angular/core';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { Subject } from 'rxjs/Subject'
export interface Alert {
message: string;
}
@Injectable()
export class AlertsService {
private alertTriggeredSubject = new Subject<Alert>();
alertTriggered = this.alertTriggeredSubject.asObservable();
triggerAlert(message: string) {
this.alertTriggeredSubject.next(<Alert>{ message: message });
}
}
@Injectable()
export class CustomExceptionHander {
constructor(private alertsService: AlertsService) { }
call(exception, stackTrace = null, reason = null) {
this.alertsService.triggerAlert(exception.originalException);
console.error('EXCEPTION:', exception);
}
}
@Component({
selector: 'child-component',
template : `
<h3>Child</h3>
<div id="child">
<button (click)="breakMe()">Break Me!</button>
<div>Alerts Sent:</div>
<ul><li *ngFor="let error of errors">{{error}}</li></ul>
</div>`
})
export class ChildComponent {
errors: string[] = [];
numErrors = 0
breakMe() {
this.numErrors++;
let error = `I broke it (${this.numErrors})`;
// The error added to the array below is never reflected in the
// "Alerts Sent:" <ul>...not sure why
this.errors.push(error);
console.info('ChildComponent.errors', this.errors);
// Simulate unhandled exception
throw new Error(error);
}
}
@Component({
selector: 'my-app',
template : `
<h3>Parent</h3>
<div id="parent">
<div>Alerts Received:</div>
<ul><li *ngFor="let alert of alerts">{{alert.message}}</li></ul>
<child-component></child-component>
</div>`
directives: [ChildComponent]
})
export class App implements OnInit {
constructor(private alertsService: AlertsService) { }
alerts: Alert[] = [];
ngOnInit() {
this.alertsService.alertTriggered.subscribe(alert => {
this.alerts.push(alert);
// Alert gets received, but is not reflected in the UI
// until the next alert is received, even thought the
// alerts[] is up-to-date.
console.info('App alert received:', alert);
console.info('App.alerts:', this.alerts);
});
}
}
bootstrap(App, [
AlertsService,
provide(ExceptionHandler, { useClass: CustomExceptionHander })
]).catch(err => console.error(err));
update ExceptionHandler
was renamed to ErrorHandler
https://stackoverflow.com/a/35239028/217408
orgiginal
Change detection isn't run at the end of the click
event when the handler throws.
You can invoke change detection manually but this gets a bit complicated because you need an ApplicationRef
reference and ApplicationRef
depends on ExceptionHandler
which makes a neat cycle and DI can't resolve cyclic dependencies.
A workaround is to instead of ApplicationRef
inject the Injector
and acquire AplicationRef
imperatively like
constructor(private alertsService: AlertsService, injector:Injector) {
setTimeout(() => this.appRef = injector.get(ApplicationRef));
}
and then in call
invoke change detection like
call(exception, stackTrace = null, reason = null) {
this.alertsService.triggerAlert(exception.originalException);
this.appRef.tick();
console.error('EXCEPTION:', exception);
}
Plunker example
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With