8

I have a global error handler defined as such (simplified/proprietary info scrubbed):

export class ErrorsHandler extends CommonBase implements ErrorHandler {
  constructor(protected loggingService: LoggingService,
              private coreService: CoreService {

    super(loggingService);
  }

  handleError(error: Error) {
    if (error && error.stack && (error.stack.indexOf(Constants.PACKAGE_NAME) >= 0)) {
      this.logSystemError(error, true);
      this.coreService.showSystemError(error, this.owner);
    }
    else {
      // Rethrow all other errors.
      throw error;
    }
  }

And in my module (and only my module), it's registered as a provider as such:

export function errorHandlerFactory(loggingService: LoggingService, coreService: CoreService) {
  return new ErrorsHandler(loggingService, coreService);
}

providers: [
    { provide: ErrorHandler, useFactory: errorHandlerFactory, deps: [LoggingService, CoreService] }
]

My module is consumed by others, and together we make up one large application. My problem is that ALL script errors are caught, even though I try to filter for those that are only relevant to my module/package, because the filtering is done within handleError(). And even though I rethrow errors that are not relevant to me (in the else above), developers of other modules/packages are complaining that I'm globally catching everything and the rethrown errors they get already lose certain context/information.

So the question is, is it possible to somehow limit the scope of my error handler to catch and handle ONLY script errors originating from my module/package (while completely ignore all other script errors within the application)?

After much googling, the only alternative I can think of is putting try/catch everywhere, which is something I'd like to avoid if at all possible.

Kon
  • 27,113
  • 11
  • 60
  • 86
  • 2
    https://angular.io/guide/ngmodule-faq#how-do-i-restrict-service-scope-to-a-module You have 2 options, lazy load module or provide in component. Lazy load is preferable – penleychan May 03 '19 at 18:56
  • Do you want to handle error for HTTP call alone or for the entire module ? – Robert Ellis May 08 '19 at 07:45
  • @ManojRamanan entire module. – Kon May 08 '19 at 13:17
  • Have you checked whether your ErrorHandler really goes into `else`? – Sergey May 08 '19 at 18:46
  • It surely does. – Kon May 09 '19 at 11:37
  • shouldn't you be providing ErrorsHandler, with an 's? – jgritten May 09 '19 at 21:25
  • Hey, @kon that's fairly simple if you put a check on the URL you are on. I have implemented a routing service and based on that I check if the current page is in the stack of restricted pages or not. If the current page is not in restricted ones then I throw error else I console.log the errors. – Sami Haroon May 10 '19 at 06:38
  • you can check in https://angular.io/api/common/Location and that current location object you can refer in handleError() method to check valid path called then handle the error. – Niraj May 10 '19 at 11:02
  • The problem is that by the time handleError() method gets hit, it's too late - even after I throw the error in the 'else' block, other devs say they're losing context of their errors because I catch them all originally. – Kon May 10 '19 at 15:46
  • @Kon Yep, that's the problem of global error handlers. They'll get the error first, and propagate if necessary. I remember facing a similar problem, and chose to divide up the handling into HTTP interceptors, or specially-crafted classes to handle other errors – adhirajsinghchauhan May 11 '19 at 09:38
  • This might be a dead end, but shouldn't you throw the error outside the if-else(and not just in the else branch)? those error messages might be because you have a possible execution path where you swallow the error. – ForestG May 13 '19 at 07:23
  • 1
    Here is an interesting design pattern you may want to consider: https://refactoring.guru/design-patterns/chain-of-responsibility. Instead of throwing an error you can provide an API for your users to add their own error handlers. This way the error is not rethrowned and the users have access to the full context – Baboo May 14 '19 at 11:13

2 Answers2

3

You can try creating a service - ErrorService to share the context and then throw the error from the global error handler. You can then catch the error from the required Component.

PFB the steps:

  1. Create the error service as follows:

    @Injectable({
        providedIn: 'root'
    })
    export class ErrorService {
        constructor() {}
    
        private error = new BehaviorSubject(new Error());
        error_message = this.error.asObservable();
    
        changeMessage(e: Error) {
            this.error.next(e);
        }
    }
    
  2. throw the error from the handleError method in ErrorHandler. PFB the snippet:

    handleError(error: Error) {
         if (error && error.stack &&(error.stack.indexOf(Constants.PACKAGE_NAME) >= 0)) 
         {
              this.logSystemError(error, true);
              this.coreService.showSystemError(error, this.owner);
         }
         else {
              //`errorService` is the `instance` for `ErrorService Component` 
              //imported in the defined `ErrorHandler`
              this.errorService.changeMessage(error);
              // Rethrow all other errors.
              throw error;
         }
      }
    
  3. Use try-catch to catch the error in your Component. Use error_message from ErrorService for the same.

Jahnavi Paliwal
  • 1,721
  • 1
  • 12
  • 20
2

I don't know what CommonBase is doing, and therefore I am not sure if this is a feasible answer, but one thing that you could do is change your ErrorsHandler a little bit. If you change your structure and derive from the core ErrorHandler instead of implementing its interface you could end up with something like this:

import { Injectable, ErrorHandler } from '@angular/core';

@Injectable()
export class ErrorsHandler extends ErrorHandler {

  constructor() {
    super();
  }

  handleError(error: any): void {
    if(error && error.stack && error.stack.indexOf("MyModule") >= 0) {
      console.log(`Uff, that was close. Got ${error}`);
      return;
    }
    super.handleError(error);
  }
}

I figured that this gives you a much more reliability and will propagate errors correctly. Re-throwing the error doesn't work properly somehow, but I am not 100% sure about the exact reason.

I hope that helps!

nullchimp
  • 735
  • 4
  • 13