Angular ErrorHandler - To Handle or Not to Handle?
Tags: angular, errorhandler, logging, angularlicious
Published: 2020-04-18 22:46:37

> Please visit [AngularArchitecture.com](https://www.angulararchitecture.com) to sign-up for podcast notifications and other Angular resources.

This is what you get off-the-shelf. The [ErrorHandler](https://angular.io/api/core/ErrorHandler) documentation on angular.io indicates that the default behavior is to print error messages to the console. This is fine for initial development. However, when the application goes to production, good luck trying to get access to the console of all the users of your application. 

The application is running in the wild. Anything can happen and will. It is not a question if an error will happen, it more like when errors happen, right? Therefore, if the application is important to your business and users, you will want to know some valuable information about the error:  

* When and where do errors happen?
* What kinds of error is it?
* What is the origin of the error?
* What information is contained in the error?
* What does the user need to know, if anything?
* What do we want to know about the error?
* Where can I view error information about my application.

## What is an Error?
An error indicates a problem that was not expected. The origin or source of the error may or may not be in your control.

Exceptions are a type of error that is expected or might be known to occur. Think about the scenario of a person attempting to retrieve cash from an ATM. If their balance is in the negative, the ATM will provide a message (exception) that there are no funds available. This article is mainly focused on errors from the application's perspective.

However, most web applications are going to make some HTTP calls. An HTTP call has the possibility of returning an error response. The reason for this most of the time fits into the category of a true error. The cause of the error response, in this case, is not related to the application's back end API or application. 

Some application APIs will return an **error** status code as part of the API response. This provides some indication as to the type of error. However, since the origin is the actual application's API, the response will most likely return a well-known response in a specified schema or format that will the application to handle it accordingly. For example, if the application makes a call to the *CreateAccount* API, it might return an error status code and a list of messages that indicate the reason for the error/failure. 

* User name is already taken.
* The password must contain a number and a special character.

Now, that we can expect an error condition from our application's API, we must be able to handle this specific type of error in the application. 

## External Errors/Exceptions

The Angular platform provides a mechanism to override and implement a custom `ErrorHandler` for your application. The default behavior of the default [*ErrorHandler*](https://github.com/angular/angular/blob/master/packages/core/src/error_handler.ts) is to write the messages to the browser console. Great for development and debugging. Not so good when the application is deployed to production. 

```ts
/**
 * Provides a hook for centralized exception handling.
 *
 * The default implementation of `ErrorHandler` prints error messages to the `console`. To
 * intercept error handling, write a custom exception handler that replaces this default as
 * appropriate for your app.
 *
 * @usageNotes
 * ### Example
 *
 * 
 * class MyErrorHandler implements ErrorHandler {
 *   handleError(error) {
 *     // do something with the exception
 *   }
 * }
 *
 * @NgModule({
 *   providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
 * })
 * class MyModule {}
 * 
 */
export declare class ErrorHandler {
    handleError(error: any): void;
}
export declare function wrappedError(message: string, originalError: any): Error;
```

## Angular Error Handling, Logging, and Notification

Here are some things to consider when implementing an Error Handling strategy for your application. 

### Error Handling

  * Determine where error handling should take place in the application - responsibility?
  * Single source of error handling?
  * What do you do with the error details and source?
  * Do you deliver a generic error message, "Oops!"?
  * How do you handle different types of errors?
    * HttpClient use Observables
    * Application
    * 3rd-party library
    * API/Server
  
### Error Notification
   * Determine if the end-user should be notified of the error.
   * Are there any specific messages that need to be shown to the user?
   * Should application/system administrators be notified - how?

### Error Logging (Tracking)
   * Determine what is required for logging/tracking.
   * Need to understand the context of the error.
   * Do not log too little, you require relevant and contextual information.
   * When did it occur? Where? Who? What?

### Custom Error Classes
   * instanceOf
   * extending Error Classes
   * adding rich meta data

## Error Sources

We can categorize `error sources` in (3) groups. 

1. External
2. Internal
3. Application

### External Errors

External errors are `external` from the running application. In our case, they are external to our Angular application running in a client browser. These occur on servers or APIs outside of our application's runtime environment. Server errors happen while attempting to process the request or during processing on the server.

* database connection errors
* database errors
* application exceptions
* application not available

#### Server
Most Angular applications use some kind of back end API(s) or server to perform additional application processing. Even if the  Angular application is `serverless` - meaning that it doesn't have its specific server associated with the application, the application may use several APIs and functions that are hosted by other providers (think: APIs for MailChimp, Contentful, Firebase, Medium, etc.).

Regardless of the source of these `external` errors, an Angular application will need to handle them gracefully.

* 500 Errors

    > The server failed to fulfill a request.

    >   Response status codes beginning with the digit "5" indicate cases in which the server 
    >   is aware that it has encountered an error or is otherwise incapable of performing the 
    >   request. Except when responding to a HEAD request, the server should include an entity 
    >   containing an explanation of the error situation, and indicate whether it is a 
    >   temporary or permanent condition. Likewise, user agents should display any included 
    >   entity to the user. These response codes apply to any request method.

Here is an example of some of the types of `500` Server Errors that can happen.

* 500 Internal Server Error
  > A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.[62]
* 501 Not Implemented
  > The server either does not recognize the request method, or it cannot fulfill the request. Usually, this implies future availability (e.g., a new feature of a web-service API).[63]
* 502 Bad Gateway
  > The server was acting as a gateway or proxy and received an invalid response from the upstream server.[64]
* 503 Service Unavailable
  > The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state.[65]

### Internal Errors

An internal error origin is from the application's API. These types of error, as mentioned previously, will most like use a specific HTTP error status code. However, it will also include a detailed response in a known format to allow the consumer of the API to handle the response. Even though the HTTP status code is an error code, the application should 

* Security and/or a permission issue.
* Business rule violation(s).
* Data validation exception(s).

#### Server
* 400 Errors

        This class of status code is intended for situations in which the error seems to have been caused by the client. Except when responding to a HEAD 
        request, the server should include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition. These status codes apply to any request method. User agents should display any included entity to the user.

#### Client (Browser) - JavaScript

JavaScript has an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object that all errors in JavaScript derive from. The standard available properties for an error are as follows:

* columnNumber
* fileName
* lineNumber
* message
* name
* stack

This is the information that we see in the Console of the browser's developer tools. These types of errors are usually unexpected

Here is a list of specialized types of errors that can occur.

* [EvalError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError)
* [InternalError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/InternalError)
* [RangeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError)
* [ReferenceError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError)
* [SyntaxError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError)
* [TypeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError)
* [URIError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError)

### Application Errors

Applications can also be the source of `errors`. These types of errors cause the current application flow to redirect to a registered provider for `Handling` the error. Developers, coders, and software engineers will not write perfect code. There are inputs, outputs, processing of information, algorithms, calculations, and other things that happen during the runtime of an application that it is impossible to anticipate all things. 

Therefore, errors happen and we will see them in the following cases:

1. Business Rule Violations
2. Data Validation Errors
3. Application Exceptions

## Error Handling
Regardless of the origination of an error, an Angular application needs to handle the error. Angular has an `ErrorHandler` that is provided to the application when the application is `initialized`. This `ErrorHandler` will eventually `catch` and `handle` all *thrown* errors.

```ts
import {ERROR_ORIGINAL_ERROR, getDebugContext, getErrorLogger, getOriginalError} from './errors';

export class ErrorHandler {
  /**
   * @internal
   */
  _console: Console = console;

  handleError(error: any): void {
    const originalError = this._findOriginalError(error);
    const context = this._findContext(error);
    // Note: Browser consoles show the place from where console.error was called.
    // We can use this to give users additional information about the error.
    const errorLogger = getErrorLogger(error);

    errorLogger(this._console, `ERROR`, error);
    if (originalError) {
      errorLogger(this._console, `ORIGINAL ERROR`, originalError);
    }
    if (context) {
      errorLogger(this._console, 'ERROR CONTEXT', context);
    }
  }

  /** @internal */
  _findContext(error: any): any {
    if (error) {
      return getDebugContext(error) ? getDebugContext(error) :
                                      this._findContext(getOriginalError(error));
    }

    return null;
  }

  /** @internal */
  _findOriginalError(error: Error): any {
    let e = getOriginalError(error);
    while (e && getOriginalError(e)) {
      e = getOriginalError(e);
    }

    return e;
  }
}

export function wrappedError(message: string, originalError: any): Error {
  const msg = `${message} caused by: ${originalError instanceof Error ? originalError.message: originalError }`;
  const error = Error(msg);
  (error as any)[ERROR_ORIGINAL_ERROR] = originalError;
  return error;
}
```

The actual code for the Angular `ErrorHandler` contains comments and an example.

> Provides a hook for centralized exception handling. The default implementation of `ErrorHandler` prints error messages to the `console`. To intercept error handling, write a custom exception handler that replaces this default as appropriate for your app.

The code sample provided shows that we can create our class that implements the `ErrorHandler` interface. A custom handler will need to override and provide a concrete implementation of the `handleError()` method.

```ts
class MyErrorHandler implements ErrorHandler {
  handleError(error) {
    // do something with the exception
  }
}
```

To allow a specific `NgModule` to use the custom Error Handler, use the `providers` configuration and the `useClass` property with the type of the new `ErrorHandler`. 

> Typically, you would provide the ErrorHandler for the entire application in the `AppModule`. However, each module can have its handler scoped at the module level.

```ts
@NgModule({
  providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
})
class MyModule {}
```

### Errors from the Dark Side:: The Back End

Many times, the error is from the `Back End` of the application - the Web API. If an error occurs on the back end, you will typically get a 400 or 500 status code from the server. However, during the processing of an HTTP request, it is also possible to get an error. These errors may be connection related or an error in the processing of the HTTP request or the response. There is a lot of opportunities for things to go wrong. 

For example, if you use `HttpClient` you can call the `request()` method. Using the *rxjs* `pipe()`, you can also use the `catchError()` which will return an `HttpErrorResponse` to be handled. 

```ts
execute<T>(requestOptions: HttpRequestOptions): Observable<HttpResponse<ApiResponse<T>>> {
    try {
      return this.httpClient.request<T>(
        requestOptions.requestMethod.toString(),
        requestOptions.requestUrl,
        {
          headers: requestOptions.headers,
          observe: requestOptions.observe,
          params: requestOptions.params,
          reportProgress: requestOptions.reportProgress,
          withCredentials: requestOptions.withCredentials
        }
      ).pipe(
        catchError((errorResponse: any) => {
          return this.handleError(errorResponse);
        })
      );
    } catch (error) {
      this.handleError(error);
    }
  }
```

The `HttpErrorResponse` contains details to determine the **source** of the error. Was it from the server/http or from within the application. This will help you to determine what type of information to provide the user, if any. At a minimum, you could log this information to help monitor the health of the application and determine if any improvements should be made.

[HttpErrorResponse](https://angular.io/api/common/http/HttpErrorResponse): A response that represents an error or failure, either from a non-successful HTTP status - an error while executing the request, or some other failure which occurred during the 
parsing of the response.

I updated the signature of the `handleError()` method to include either type of `Error` or `HttpErrorResponse` - this will allow for specialized handling based on the type of error.

```ts
protected handleError(error: Error | HttpErrorResponse): Observable<any> {
  if(error.error instanceof ErrorEvent)  {
    // A client-side or network error occurred. Handle it accordingly.
  } else {
      // The API returned an unsuccessful response.
  }
  // handler returns an RxJS ErrorObservable with a user-friendly error message. Consumers of the service expect service methods to return an Observable of some kind, even a "bad" one.
  // return throwError(error);
  return throwError(`Hey, you got my chocolate in your peanut butter.`);
}
```

> Notice that the `HttpErrorResponse` type implements `Error`. Therefore, it contains information about the HTTP Request and also error information, like the *stack* trace if available. 

```ts
class HttpErrorResponse extends HttpResponseBase implements Error {
  constructor(init: {...})
  get name: 'HttpErrorResponse'
  get message: string
  get error: any | null
  get ok: false

  // inherited from common/http/HttpResponseBase
  constructor(init: {...}, defaultStatus: number = 200, defaultStatusText: string = 'OK')
  get headers: HttpHeaders
  get status: number
  get statusText: string
  get url: string | null
  get ok: boolean
  get type: HttpEventType.Response | HttpEventType.ResponseHeader
}
```

The abstract base class for the HttpResponse provides the structure for other **HTTP Response** classes:

* HttpErrorResponse
* HttpHeaderResponse
* HttpResponse

```ts
abstract class HttpResponseBase {
  constructor(init: {...}, defaultStatus: number = 200, defaultStatusText: string = 'OK')
  get headers: HttpHeaders
  get status: number
  get statusText: string
  get url: string | null
  get ok: boolean
  get type: HttpEventType.Response | HttpEventType.ResponseHeader
}
```

## Custom Error Handler

Create a new class for the custom `ErrorHandler`.

```ts
ng generate class myErrorHandler --project=error-handling --spec=false
```

```ts
import { ErrorHandler } from "@angular/core";

export class MyErrorHandler implements ErrorHandler {
    handleError(error: any): void {
        throw new Error("Method not implemented.");
    }
}
```

Add a reference to the library module. We will need to import `HttpClientModule`. This will give us access to the `ErrorHandler` interface that we will need to implement.

```ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
  imports: [
    CommonModule,
    HttpClientModule
  ]
})
export class ErrorHandlingModule {}
```

Implement the interface.

```ts
import { ErrorHandler } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';

export class MyErrorHandler implements ErrorHandler {
    handleError(error: Error | HttpErrorResponse): Observable<any> {
        throw new Error('Method not implemented.');
    }
}
```

The following implementation is doing a few things as a sample implementation.

* uses a configuration service (injected); use to provide information on how to handle writing error events
* uses a logging service (injected); used to allow the error handler to log information to a target

```ts
import { Injectable, ErrorHandler } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { noop } from 'rxjs';

import { ConfigurationService, ErrorHandlingConfig, IConfiguration, IErrorHandingConfig } from '@tc/configuration';
import { LoggingService, Severity } from '@tc/logging';

@Injectable({
  providedIn: 'root'
})
export class ErrorHandlingService extends ErrorHandler {
  serviceName = 'ErrorHandlingService';
  errorHandlingConfig: IErrorHandingConfig;
  hasSettings: boolean;

  constructor(private config: ConfigurationService, private loggingService: LoggingService) {
    super();

    this.init();
  }

  init() {
    // Use to provide default settings for error handling processing.
    this.errorHandlingConfig = new ErrorHandlingConfig('ErrorHandler', true);
    this.loggingService.log(this.serviceName, Severity.Warning, `Application [ErrorHandler] is using default settings`);

    // subscribe and use settings from the [ConfigurationService] when available.
    this.config.settings$.subscribe(settings => this.handleSettings(settings));
  }

  handleSettings(settings: IConfiguration) {
    this.errorHandlingConfig = settings.errorHandling;
    this.hasSettings = true;
    this.loggingService.log(this.errorHandlingConfig.name, Severity.Information, `Application [ErrorHandler] using configuration settings.`);
  }

  handleError(error: Error | HttpErrorResponse): any {
    if (this.errorHandlingConfig.includeDefaultErrorHandling) {
      // use the [super] call to keep default error handling functionality --> console;
      super.handleError(error);
    }

    if (this.hasSettings) {
      // A. HANDLE ERRORS FROM HTTP
      if (error instanceof HttpErrorResponse) {
        if (error.error instanceof ErrorEvent) {
          // A.1: A client-side or network error occurred. Handle it accordingly.
          const formattedError = `${error.name}; ${error.message}`;
          this.loggingService.log(this.errorHandlingConfig.name, Severity.Error, `${formattedError}`);
        } else {
          // A.2: The API returned an unsuccessful response (i.e., 400, 401, 403, etc.).
          /**
           * The [HttpService] should return a response that is consumable by the caller
           * of the API. The response should include relevant information and error messages
           * in a format that is known and consumable by the caller of the API.
           */
          noop();
        }
      } else {
        // B. HANDLE A GENERALIZED ERROR FROM THE APPLICATION/CLIENT;
        const formattedError = `${error.name}; ${error.message}}`;
        this.loggingService.log(this.errorHandlingConfig.name, Severity.Error, `${formattedError}`, error.stack ? error.stack : null);
      }
    }
  }
}
```

### One Error Handler for Different Types of Errors

#### A1: HttpErrorResponse & ErrorEvent Handling
The signature of the *handleError* method can be either of (2) types: *Error* or *HttpErrorResponse*. One of the first things to do is to determine if the origin of the error is an *HttpErrorResponse* or not. If it is of types *HttpErrorResponse* and *ErrorEvent*, then it is either an application/client or network related error. Therefore, we will write this error to the application log. 

#### A2: HttpErrorResponse Handling (No Handling Required)
If it is of type *HttpErrorResponse* only, then the origin is most likely the application's API/application back end. Therefore, the application should be able to handle the response (event though it is in an error state), because the response should be in a well-known API response format. There is no additional processing by the *ErrorHandler* for these types of errors. 

An API response should be well-defined and known to the consumers of the API. A typical response either *success* or *failure* should contain a common set of properties. The following is an example of a schema that can be used to provide response information to the application.  

* IsSuccess: A *boolean* value used to indicate if the result of the request is a success or not. This should be set to false if the HTTP status code is an error status. 
* Message: A general message regarding the request (i.e., "Successfully created a new account."). 
* StatusCode: A valid HTTP status code.
* Timestamp: A value indicating the date and time of the response. 

```ts
export abstract class ApiResponse<T> {
    IsSuccess: boolean;
    Message: string;
    StatusCode: number;
    Timestamp: Date;
  }
```

A *success* response will extend from the abstract base class `ApiResponse<T>`. The `Data` payload will be in a known and defined type. The JSON data payload should map to a specific model by the application. 

```ts
import { ApiResponse } from './api-response';

/**
 * Use to define a successful API response. A successful response will
 * most likely include a payload of data (i.e., use the Data property). 
 */
export class SuccessApiResponse<T> extends ApiResponse<T> {
  Data: T;
}
```

A failure* response will also extend from the abstract base class `ApiResponse<T>`. Instead of having a `Data` payload, it will have a list of `ApiErrorMessage` items to provide additional information to the application. This may include a message that could be displayable to the user. 

```ts
import { ApiResponse } from './api-response';
import { ApiErrorMessage } from './api-error-message';

/**
 * Use to provide error information from an API. You can also 
 * use this class to create a response with errors while doing
 * error handling.
 * 
 * Errors: is a list om [ApiErrorMessage] items that contain specific
 * errors for the specified request. 
 */
export class ErrorApiResponse<T> extends ApiResponse<T> {
  Errors: ApiErrorMessage[] = [];
}
```

The specified error message items should also be well-defined and known by the application. 

```ts
export class ApiErrorMessage {
    id?: string;
    statusCode?: string;
    message: string;
    isDisplayable: boolean;
  
    /**
     * Use to create a new [ApiErrorMessage]
     * @param message The error from the API.
     * @param displayable Use to indicate if the error should be displayed to the user.
     * @param id An optional identifier for the error.
     * @param statusCode An optional status code for the specified error.
     */
    constructor(message: string, displayable: boolean, id: string | null, statusCode: string | null) {
      this.message = message;
      this.isDisplayable = displayable;
      if (id) {
        this.id = id;
      }
      if (statusCode) {
        this.statusCode = statusCode;
      }
    }
  }
```


#### B: General Error from the Application/Browser Client

This type of error requires handling (i.e., logging to a centralized repository and/or console log). These are most likely errors that should be monitored and reviewed by stakeholders of the application.

```ts
  handleError(error: Error | HttpErrorResponse): any {
    if (this.errorHandlingConfig.includeDefaultErrorHandling) {
      // use the [super] call to keep default error handling functionality --> console;
      super.handleError(error);
    }

    if (this.hasSettings) {
      // A. HANDLE ERRORS FROM HTTP
      if (error instanceof HttpErrorResponse) {
        if (error.error instanceof ErrorEvent) {
          // A.1: A client-side or network error occurred. Handle it accordingly.
          const formattedError = `${error.name}; ${error.message}`;
          this.loggingService.log(this.errorHandlingConfig.name, Severity.Error, `${formattedError}`);
        } else {
          // A.2: The API returned an unsuccessful response (i.e., 400, 401, 403, etc.).
          /**
           * The [HttpService] should return a response that is consumable by the caller
           * of the API. The response should include relevant information and error messages
           * in a format that is known and consumable by the caller of the API.
           */
          noop();
        }
      } else {
        // B. HANDLE A GENERALIZED ERROR FROM THE APPLICATION/CLIENT;
        const formattedError = `${error.name}; ${error.message}}`;
        this.loggingService.log(this.errorHandlingConfig.name, Severity.Error, `${formattedError}`, error.stack ? error.stack : null);
      }
    }
  }
```

To use `MyErrorHandler` as the error handler (instead of Angular's default), update the application's `AppModule` with a provider item that uses the new class.

```ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';

import { AppComponent } from './app.component';
import { NxModule } from '@nrwl/nx';
import { RouterModule } from '@angular/router';
import { MyErrorHandler } from '@my/error-handling';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    NxModule.forRoot(),
    RouterModule.forRoot([], { initialNavigation: 'enabled' })
  ],
  providers: [
    {
      provide: ErrorHandler,
      useClass: MyErrorHandler
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
```

## References

* [Error Handling and Angular](https://medium.com/@aleixsuau/error-handling-angular-859d529fa53a)
* [HTTP Status Codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_errors)
* [JavaScript Error Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
* [Exceptional Exception Handling in Javascript](https://www.sitepoint.com/exceptional-exception-handling-in-javascript/)
* [Angular ErrorHandler (error_handler.ts)](https://github.com/angular/angular/blob/master/packages/core/src/error_handler.ts)
* [Angular HttpClient :: Error Handling](https://angular.io/guide/http)
* [Angular HttpResponse]( https://angular.io/api/common/http/HttpErrorResponse)
* [Angular HttpResponseBase](https://angular.io/api/common/http/HttpResponseBase)
* [Chocolate in my Peanut Butter](https://www.youtube.com/watch?v=DJLDF6qZUX0)

## Resources

* [Error Handling & Angular - Shit happens, deal with it](https://medium.com/@aleixsuau/error-handling-angular-859d529fa53a)
* [C# Exception Handling Best Practices](https://stackify.com/csharp-exception-handling-best-practices/)