Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom ExceptionHandler change detection lag

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));
like image 723
WayneC Avatar asked Jun 13 '16 15:06

WayneC


1 Answers

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

like image 186
Günter Zöchbauer Avatar answered Oct 15 '22 16:10

Günter Zöchbauer