11

When an exception is caught by Angular 2's exception handler, the UI no longer 'updates'.

I have a very simple example 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;
}

@Component({
  selector: 'my-app',
  template : `
  <h3>Parent</h3>
  {{aValue}}
  <br/>
  <br/>
  <button (click)='doIt()'>do it</button>
  <br/>
  <br/>
  <button (click)='breakIt()'>break it</button>
  `
})

export class App implements OnInit {
  private aValue: boolean = true
  constructor() { }
  
  alerts: Alert[] = [];
  
  doIt(){
    console.log('Doing It')
    this.aValue = !this.aValue
  }
  
  breakIt(){
    console.log('Breaking It')
    throw new Error('some error')
  }
}

bootstrap(App).catch(err => console.error(err));

The Do It button flips the boolean which is reflected in the interpolated value in the template. However, once the Break It button is pressed (which cases an error to be thrown), the interpolated value no longer updates when the 'Do it' button is hit. However, the console still logs the messages Doing it.

The problem I'm dealing with is I would like to create a custom exception handler that warns the user and possibly does some work to clear a form when something has gone wrong, but what I'm seeing is if I throw an error on a button click to test it, all UI updates stop. However, buttons continue to function, meaning any buttons that post requests will do so if the form is in a good state. The UI however has ceased to update and inform the user what is happening.

I'm not sure if this is a problem with Zones or something else, but attempting an NgZone.run() didn't seem to solve the problem for me. If an error is meant to break a component's UI, what's the correct approach to my problem?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Bernard Ng
  • 111
  • 1
  • 4
  • See also http://stackoverflow.com/questions/37793276/angular-2-custom-exceptionhandler-change-detection-lag/37793791#37793791 – Günter Zöchbauer Jun 15 '16 at 13:14
  • Interesting. The difference I'm seeing is the error is thrown by a child component, and his plunker's view continues to update. Does this mean I just need to try...catch on the parent component and all child component errors will not cause the view to break? – Bernard Ng Jun 15 '16 at 14:54
  • I don't think it's easy to make a general statement. This always depends on where the code that throws was called from and if that breaks anything if some code was called but didn't complete with an expected path. – Günter Zöchbauer Jun 15 '16 at 15:41
  • Noted. In my testing of this hypothesis though, it appears to be true. I think I have a way forward... possibly a parent component that wraps everything else, and acts as a last line of defense for error handling. – Bernard Ng Jun 15 '16 at 17:30
  • Very bad hack https://plnkr.co/edit/FxVpqlMueuiMSDFQtS38?p=preview – yurzui Jun 15 '16 at 18:40
  • @yurzui, what is cdState? And why does setting it to 1 work? – Bernard Ng Jun 16 '16 at 16:05

2 Answers2

5

Since angular 4.1.1 (2017-05-04) https://github.com/angular/angular/commit/07cef36

fix(core): don’t stop change detection because of errors

  • prevents unsubscribing from the zone on error
  • prevents unsubscribing from directive EventEmitters on error
  • prevents detaching views in dev mode if there on error
  • ensures that ngOnInit is only called 1x (also in prod mode)

it should work without additional code

@Component({
  selector: 'my-app',
  template : `
    {{aValue}}
    <button (click)='doIt()'>do it</button>
    <button (click)='breakIt()'>break it</button>
  `
})

export class App implements OnInit {
  private aValue: boolean = true

  doIt(){
    console.log('Doing It')
    this.aValue = !this.aValue
  }

  breakIt(){
    console.log('Breaking It')
    throw new Error('some error')
  }
}

Plunker Example

yurzui
  • 205,937
  • 32
  • 433
  • 399
1

Don't rely on code execution after an unhandled Exception happened. You have to handle the exception in the place where you expect it to happen.

Error handling: http://www.javascriptkit.com/javatutors/trycatch.shtml

Andrei Zhytkevich
  • 8,039
  • 2
  • 31
  • 45
  • 1
    I'm trying to implement Angular 2's global exception handling in the hopes of not having to pepper my all my code with try...catchs, but the scenario I described was making me think I couldn't take this approach. I did look at the question Günter pointed me to, though and now I'm wondering if I can get away with just try-catches on parent components, since child components in that question was not breaking view updates. I don't necessarily expect anything to break, but my tech lead is adamant that error catching happens everywhere regardless of what I expect. – Bernard Ng Jun 15 '16 at 15:00
  • You don't need to wrap everything in try/catch. Exceptions are your friends during development. Handling exceptions make sense for anything a user inputs into your application, because we know that an end-user can do crazy things. Exceptions help you to catch bad things you are doing. – Andrei Zhytkevich Jun 15 '16 at 15:06
  • 5
    That's what I've always done in the past, and I agree that exceptions are my friends. The problem here is in production we want to post errors to the server and also inform the user that something bad has happened. If Angular doesn't update its view anymore, I can't rely on Angular to do that. I might fallback to pure javascript in the exception handler, but I was trying to avoid that as well. – Bernard Ng Jun 15 '16 at 15:15