Error handler ошибка

In this tutorial, we look at how error handling in Angular. We also learn how to create a Global Error handler or custom error handler in Angular. We learn why we need to handle errors and some of the best practices. In the end we will learn few tips like how to Inject services to global error handler, How to show user notification page etc.

Table of Contents

  • Why Handle Errors
    • HTTP Errors
    • Client Side Errors
  • Default Error Handling in Angular
    • Error Handling Example
  • Global Error Handler
    • How to Create Global Error Handler ?
  • Error Handler Example
  • Best Practices in Handling Errors
  • Tips for Error Handler
    • Injecting other services to the global error handler
    • User Notification Page
    • Handling HTTP Errors
  • References

Why Handle Errors

Handling error is an important part of the application design. The JavaScript can throws errors each time something goes wrong. For Example, the Javascipt throws errors in the following conditions

  1. When we reference a non-existent variable.
  2. The value provided is not in the range of allowed values.
  3. When Interpreting syntactically invalid code
  4. When a value is not of the expected type
  5. Internal errors in the JavaScript engine

The apart from the above, the unexpected errors can happen any time. like broken connection, null pointer exception, no internet, HTTP errors like unauthorized user, session expired etc.

The Angular handles the errors, but it wont do anything except writing it the console. And that is not useful either to the user or to the developer.

There are two types of error handling mechanism in Angular. One catches all the client side errors and the other one catches the HTTP Errors.

HTTP Errors

The HTTP Errors are thrown, when you send a HTTP Request using the HttpClient Module. The errors again falls into two categories. One is generated by the server like unauthorized user, session expired, Server down etc. The Other one is generated at the client side, while trying to generate the HTTP Request. These errors could be network error, error while generating the request etc

The HTTP errors are handled by the HTTP Interceptors

Client Side Errors

All other errors thrown by the code falls into this category. These are are handled by the ErrorHandler class, which is the default error handler for Angular.

The default Error handling in Angular is handled by Errorhandler class, which is part of the @angular/core module. This is global error handler class which catches all exception occurring in the App. This class has a method handleError(error). Whenever the app throws an unhandled exception anywhere in the application angular intercepts that exception. It then invokes the method handleError(error) which writes the error messages to browser console.

Error Handling Example

Create a new Angular application. Add the following code snippet to app.component.html & app.component.ts

app.component.html

<h1> {{title}} </h1>

<button (click)=«throwError1()»> Throw Error1 </button>

<button (click)=«throwError2()»> Throw Error2 </button>

<routeroutlet></routeroutlet>

app.component.ts

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

import { Component } from ‘@angular/core’;

@Component({

  selector: ‘app-root’,

  templateUrl: ‘./app.component.html’,

  styleUrls: [‘./app.component.css’]

})

export class AppComponent

{

    title: string = ‘Global Error Handler in Angular’ ;

    throwError1() {

      var a= b;  

    }

    throwError2() {

      try {

        var a= b;  

      } catch (error) {

         //here you can handle the error

         //

      }

    }

}

The code mimics an error by using the statementvar a= b;, where b is not defined. The first method throwError1() does not handle error, while throwError2() method uses try..catch block to handle the error.

Run the app and keep the chrome developer tool open. Click on throw error 1 button. The default Error Handler of angular intercepts the error and writes to the console as shown in image below

But, clicking on the throw error 2 button, does not trigger the Error Handler as it is handled by using the try..catch block.

If you are not handling the error in the try..catch block, then you must use throw error so that the default error handler can catch it.

    throwError2() {

      try {

        var a= b;  

      } catch (error) {

        throw error;    //rethrow the error

      }

    }

Global Error Handler

The built in ErrorHandler is simple solution and provides a good option while developing the app. But it does not help to find out the error thrown in the the production environment. We have no way of knowing about the errors which happen at the users end.

Hence, it advisable to create our own global error handler class, because

  1. We can show a simple error page to the user, with a option to retry the operation
  2. We can log the errors back to the back end server, where we can read all the errors. Then we can make necessary changes to the app to remove the error

How to Create Global Error Handler ?

To create a custom error handler service, we need to use the following steps.

First.create a GlobalErrorHandlerService  which implements the ErrorHandler
Then, override the handleError(error) method and handle the error.

export class GlobalErrorHandlerService implements ErrorHandler {

  constructor() {

  }

  handleError(error) {

     console.error(‘An error occurred:’, error.message);

  }

}

Next, register the GlobalErrorHandlerService in the Application root module using the token ErrorHandler.

@NgModule({

  ——

  providers: [

    { provide: ErrorHandler, useClass: GlobalErrorHandlerService },

  ]

})

export class AppModule { }

Error Handler Example

Create global-error-handler.service.ts and add the following code.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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

@Injectable()

export class GlobalErrorHandlerService implements ErrorHandler {

    constructor() {

    }

    handleError(error) {

       console.error(‘An error occurred:’, error.message);

       console.error(error);

       alert(error);

   }

}

Next, open the pp.module.ts and register the GlobalErrorHandlerService using the injection token ErrorHandler.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

import { BrowserModule } from ‘@angular/platform-browser’;

import { NgModule,ErrorHandler } from ‘@angular/core’;

import { AppComponent } from ‘./app.component’;

import  {GlobalErrorHandlerService} from ‘./global-error-handler.service’;

@NgModule({

  declarations: [

    AppComponent

  ],

  imports: [

    BrowserModule,

  ],

  providers: [

    { provide: ErrorHandler, useClass: GlobalErrorHandlerService },

  ],

  bootstrap: [AppComponent]

})

export class AppModule { }

Run the app and you will see that the our custom error handler gets invoked, when you click on the button throw error.

Best Practices in Handling Errors

Now, we learned how to handle errors, here are a few things you should keep in mind while designing an Error Handler service.

  1. Use a try.. catch block to handle the known errors. Handle it accordingly. If you are not able to handle it, then re-throw it.
  2. Use a global error handler to trap all unhandled errors and show a notification to the user.
  3. The ErrorHandler does not trap HTTP Errors, You need to Use HTTP Interceptors to handle HTTP Errors. You can refer to this article how to handle HTTP Errors in Angular.
  4. Check for type of error in the error handler and act accordingly.
    • For Example, if is an error from the back end (HTTP Error) you can use the HTTP Status Code to take necessary action.
    • 401 Unauthorized error you can redirect the user to the login page.
    • 500 Internal Server Error you can ask the user to retry after some time while sending a notification to the server administrator e
  5. For all other unhandled errors, log the errors back to the back end server ( or to any third party error providers). You can then look at those logs and make necessary changes to the app.

Tips for Error Handler

Injecting other services to the global error handler

The Angular creates the error handler service before the providers. Otherwise, it won’t be able catch errors that occur very early in the application. It also means that the angular providers won’t be available to the ErrorHandler.

What if we wanted to use another service in the error handler. Then, we need to use the Injector instance to directly to inject the dependency and not depend on the Dependency injection framework

To do that first we need to import the injector

Then we need to inject the injector to the GlobalErrorHandlerService.

Finally, use the injector to get the instance of any required service.

The following example service uses the injector to get the Router Service.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

import { ErrorHandler, Injectable, Injector} from ‘@angular/core’;

import { Router } from ‘@angular/router’;

@Injectable()

export class GlobalErrorHandlerService implements ErrorHandler {

    constructor(private injector: Injector) {

    }

    handleError(error) {

        let router = this.injector.get(Router);

        console.log(‘URL: ‘ + router.url);

        console.error(‘An error occurred:’, error.message);

       alert(error);

   }

}

User Notification Page

It is a good design practice to notify the user regarding the error by using the error page.

error.component .ts

import { Component } from ‘@angular/core’;

@Component({

  template: `

     <h2>An unknown error occurred.</h2>

  `

})

export class ErrorComponent {

}

Do not forget to add it in the routing Module.

app-routing.module.ts

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import { NgModule } from ‘@angular/core’;

import { Routes, RouterModule } from ‘@angular/router’;

import { ErrorComponent } from ‘./error.component ‘;

const routes: Routes = [

  {path: ‘error’, component: ErrorComponent }

]

@NgModule({

  imports: [RouterModule.forRoot(routes)],

  exports: [RouterModule],

  providers: []

})

export class AppRoutingModule { }

And in the GlobalErrorHandlerService, inject router and use router.navigate(['/error']) to go to the custom error page

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

import { ErrorHandler, Injectable, Injector} from ‘@angular/core’;

import { Router } from ‘@angular/router’;

@Injectable()

export class GlobalErrorHandlerService implements ErrorHandler {

    constructor(private injector: Injector) {    }

    handleError(error) {

        let router = this.injector.get(Router);

        console.log(‘URL: ‘ + router.url);

        console.error(error);

        router.navigate([‘/error’]);

   }

}

Handling HTTP Errors

You can refer to the tutorial HTTP Error Handling in Angular

References

Error Handler API

Please visit AngularArchitecture.com to sign-up for podcast notifications and other Angular resources.

This is what you get off-the-shelf. The 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 is to write the messages to the browser console. Great for development and debugging. Not so good when the application is deployed to production.

/**
 * 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;

Enter fullscreen mode

Exit fullscreen mode

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 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
  • InternalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • 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.

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;
}

Enter fullscreen mode

Exit fullscreen mode

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.

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

Enter fullscreen mode

Exit fullscreen mode

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.

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

Enter fullscreen mode

Exit fullscreen mode

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.

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);
    }
  }

Enter fullscreen mode

Exit fullscreen mode

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: 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.

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.`);
}

Enter fullscreen mode

Exit fullscreen mode

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.

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
}

Enter fullscreen mode

Exit fullscreen mode

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

  • HttpErrorResponse
  • HttpHeaderResponse
  • HttpResponse
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
}

Enter fullscreen mode

Exit fullscreen mode

Custom Error Handler

Create a new class for the custom ErrorHandler.

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

Enter fullscreen mode

Exit fullscreen mode

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

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

Enter fullscreen mode

Exit fullscreen mode

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.

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

Enter fullscreen mode

Exit fullscreen mode

Implement the interface.

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.');
    }
}

Enter fullscreen mode

Exit fullscreen mode

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
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);
      }
    }
  }
}

Enter fullscreen mode

Exit fullscreen mode

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.
export abstract class ApiResponse<T> {
    IsSuccess: boolean;
    Message: string;
    StatusCode: number;
    Timestamp: Date;
  }

Enter fullscreen mode

Exit fullscreen mode

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.

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;
}

Enter fullscreen mode

Exit fullscreen mode

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.

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[] = [];
}

Enter fullscreen mode

Exit fullscreen mode

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

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;
      }
    }
  }

Enter fullscreen mode

Exit fullscreen mode

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.

  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);
      }
    }
  }

Enter fullscreen mode

Exit fullscreen mode

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.

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 {}

Enter fullscreen mode

Exit fullscreen mode

References

  • Error Handling and Angular
  • HTTP Status Codes
  • JavaScript Error Object
  • Exceptional Exception Handling in Javascript
  • Angular ErrorHandler (error_handler.ts)
  • Angular HttpClient :: Error Handling
  • Angular HttpResponse
  • Angular HttpResponseBase
  • Chocolate in my Peanut Butter

Resources

  • Error Handling & Angular — Shit happens, deal with it
  • C# Exception Handling Best Practices

Error Handling refers to how Express catches and processes errors that
occur both synchronously and asynchronously. Express comes with a default error
handler so you don’t need to write your own to get started.

Catching Errors

It’s important to ensure that Express catches all errors that occur while
running route handlers and middleware.

Errors that occur in synchronous code inside route handlers and middleware
require no extra work. If synchronous code throws an error, then Express will
catch and process it. For example:

app.get('/', (req, res) => {
  throw new Error('BROKEN') // Express will catch this on its own.
})

For errors returned from asynchronous functions invoked by route handlers
and middleware, you must pass them to the next() function, where Express will
catch and process them. For example:

app.get('/', (req, res, next) => {
  fs.readFile('/file-does-not-exist', (err, data) => {
    if (err) {
      next(err) // Pass errors to Express.
    } else {
      res.send(data)
    }
  })
})

Starting with Express 5, route handlers and middleware that return a Promise
will call next(value) automatically when they reject or throw an error.
For example:

app.get('/user/:id', async (req, res, next) => {
  const user = await getUserById(req.params.id)
  res.send(user)
})

If getUserById throws an error or rejects, next will be called with either
the thrown error or the rejected value. If no rejected value is provided, next
will be called with a default Error object provided by the Express router.

If you pass anything to the next() function (except the string 'route'),
Express regards the current request as being an error and will skip any
remaining non-error handling routing and middleware functions.

If the callback in a sequence provides no data, only errors, you can simplify
this code as follows:

app.get('/', [
  function (req, res, next) {
    fs.writeFile('/inaccessible-path', 'data', next)
  },
  function (req, res) {
    res.send('OK')
  }
])

In the above example next is provided as the callback for fs.writeFile,
which is called with or without errors. If there is no error the second
handler is executed, otherwise Express catches and processes the error.

You must catch errors that occur in asynchronous code invoked by route handlers or
middleware and pass them to Express for processing. For example:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('BROKEN')
    } catch (err) {
      next(err)
    }
  }, 100)
})

The above example uses a try...catch block to catch errors in the
asynchronous code and pass them to Express. If the try...catch
block were omitted, Express would not catch the error since it is not part of the synchronous
handler code.

Use promises to avoid the overhead of the try...catch block or when using functions
that return promises. For example:

app.get('/', (req, res, next) => {
  Promise.resolve().then(() => {
    throw new Error('BROKEN')
  }).catch(next) // Errors will be passed to Express.
})

Since promises automatically catch both synchronous errors and rejected promises,
you can simply provide next as the final catch handler and Express will catch errors,
because the catch handler is given the error as the first argument.

You could also use a chain of handlers to rely on synchronous error
catching, by reducing the asynchronous code to something trivial. For example:

app.get('/', [
  function (req, res, next) {
    fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
      res.locals.data = data
      next(err)
    })
  },
  function (req, res) {
    res.locals.data = res.locals.data.split(',')[1]
    res.send(res.locals.data)
  }
])

The above example has a couple of trivial statements from the readFile
call. If readFile causes an error, then it passes the error to Express, otherwise you
quickly return to the world of synchronous error handling in the next handler
in the chain. Then, the example above tries to process the data. If this fails then the
synchronous error handler will catch it. If you had done this processing inside
the readFile callback then the application might exit and the Express error
handlers would not run.

Whichever method you use, if you want Express error handlers to be called in and the
application to survive, you must ensure that Express receives the error.

The default error handler

Express comes with a built-in error handler that takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.

If you pass an error to next() and you do not handle it in a custom error
handler, it will be handled by the built-in error handler; the error will be
written to the client with the stack trace. The stack trace is not included
in the production environment.

Set the environment variable NODE_ENV to production, to run the app in production mode.

When an error is written, the following information is added to the
response:

  • The res.statusCode is set from err.status (or err.statusCode). If
    this value is outside the 4xx or 5xx range, it will be set to 500.
  • The res.statusMessage is set according to the status code.
  • The body will be the HTML of the status code message when in production
    environment, otherwise will be err.stack.
  • Any headers specified in an err.headers object.

If you call next() with an error after you have started writing the
response (for example, if you encounter an error while streaming the
response to the client) the Express default error handler closes the
connection and fails the request.

So when you add a custom error handler, you must delegate to
the default Express error handler, when the headers
have already been sent to the client:

function errorHandler (err, req, res, next) {
  if (res.headersSent) {
    return next(err)
  }
  res.status(500)
  res.render('error', { error: err })
}

Note that the default error handler can get triggered if you call next() with an error
in your code more than once, even if custom error handling middleware is in place.

Writing error handlers

Define error-handling middleware functions in the same way as other middleware functions,
except error-handling functions have four arguments instead of three:
(err, req, res, next). For example:

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

You define error-handling middleware last, after other app.use() and routes calls; for example:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use((err, req, res, next) => {
  // logic
})

Responses from within a middleware function can be in any format, such as an HTML error page, a simple message, or a JSON string.

For organizational (and higher-level framework) purposes, you can define
several error-handling middleware functions, much as you would with
regular middleware functions. For example, to define an error-handler
for requests made by using XHR and those without:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

In this example, the generic logErrors might write request and
error information to stderr, for example:

function logErrors (err, req, res, next) {
  console.error(err.stack)
  next(err)
}

Also in this example, clientErrorHandler is defined as follows; in this case, the error is explicitly passed along to the next one.

Notice that when not calling “next” in an error-handling function, you are responsible for writing (and ending) the response. Otherwise those requests will “hang” and will not be eligible for garbage collection.

function clientErrorHandler (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' })
  } else {
    next(err)
  }
}

Implement the “catch-all” errorHandler function as follows (for example):

function errorHandler (err, req, res, next) {
  res.status(500)
  res.render('error', { error: err })
}

If you have a route handler with multiple callback functions you can use the route parameter to skip to the next route handler. For example:

app.get('/a_route_behind_paywall',
  (req, res, next) => {
    if (!req.user.hasPaid) {
      // continue handling this request
      next('route')
    } else {
      next()
    }
  }, (req, res, next) => {
    PaidContent.find((err, doc) => {
      if (err) return next(err)
      res.json(doc)
    })
  })

In this example, the getPaidContent handler will be skipped but any remaining handlers in app for /a_route_behind_paywall would continue to be executed.

Calls to next() and next(err) indicate that the current handler is complete and in what state. next(err) will skip all remaining handlers in the chain except for those that are set up to handle errors as described above.

layout title menu lang redirect_from

page

Express error handling

guide

en

/guide/error-handling.html

Error Handling

Error Handling refers to how Express catches and processes errors that
occur both synchronously and asynchronously. Express comes with a default error
handler so you don’t need to write your own to get started.

Catching Errors

It’s important to ensure that Express catches all errors that occur while
running route handlers and middleware.

Errors that occur in synchronous code inside route handlers and middleware
require no extra work. If synchronous code throws an error, then Express will
catch and process it. For example:

app.get('/', (req, res) => {
  throw new Error('BROKEN') // Express will catch this on its own.
})

For errors returned from asynchronous functions invoked by route handlers
and middleware, you must pass them to the next() function, where Express will
catch and process them. For example:

app.get('/', (req, res, next) => {
  fs.readFile('/file-does-not-exist', (err, data) => {
    if (err) {
      next(err) // Pass errors to Express.
    } else {
      res.send(data)
    }
  })
})

Starting with Express 5, route handlers and middleware that return a Promise
will call next(value) automatically when they reject or throw an error.
For example:

app.get('/user/:id', async (req, res, next) => {
  const user = await getUserById(req.params.id)
  res.send(user)
})

If getUserById throws an error or rejects, next will be called with either
the thrown error or the rejected value. If no rejected value is provided, next
will be called with a default Error object provided by the Express router.

If you pass anything to the next() function (except the string 'route'),
Express regards the current request as being an error and will skip any
remaining non-error handling routing and middleware functions.

If the callback in a sequence provides no data, only errors, you can simplify
this code as follows:

app.get('/', [
  function (req, res, next) {
    fs.writeFile('/inaccessible-path', 'data', next)
  },
  function (req, res) {
    res.send('OK')
  }
])

In the above example next is provided as the callback for fs.writeFile,
which is called with or without errors. If there is no error the second
handler is executed, otherwise Express catches and processes the error.

You must catch errors that occur in asynchronous code invoked by route handlers or
middleware and pass them to Express for processing. For example:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('BROKEN')
    } catch (err) {
      next(err)
    }
  }, 100)
})

The above example uses a try...catch block to catch errors in the
asynchronous code and pass them to Express. If the try...catch
block were omitted, Express would not catch the error since it is not part of the synchronous
handler code.

Use promises to avoid the overhead of the try...catch block or when using functions
that return promises. For example:

app.get('/', (req, res, next) => {
  Promise.resolve().then(() => {
    throw new Error('BROKEN')
  }).catch(next) // Errors will be passed to Express.
})

Since promises automatically catch both synchronous errors and rejected promises,
you can simply provide next as the final catch handler and Express will catch errors,
because the catch handler is given the error as the first argument.

You could also use a chain of handlers to rely on synchronous error
catching, by reducing the asynchronous code to something trivial. For example:

app.get('/', [
  function (req, res, next) {
    fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
      res.locals.data = data
      next(err)
    })
  },
  function (req, res) {
    res.locals.data = res.locals.data.split(',')[1]
    res.send(res.locals.data)
  }
])

The above example has a couple of trivial statements from the readFile
call. If readFile causes an error, then it passes the error to Express, otherwise you
quickly return to the world of synchronous error handling in the next handler
in the chain. Then, the example above tries to process the data. If this fails then the
synchronous error handler will catch it. If you had done this processing inside
the readFile callback then the application might exit and the Express error
handlers would not run.

Whichever method you use, if you want Express error handlers to be called in and the
application to survive, you must ensure that Express receives the error.

The default error handler

Express comes with a built-in error handler that takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.

If you pass an error to next() and you do not handle it in a custom error
handler, it will be handled by the built-in error handler; the error will be
written to the client with the stack trace. The stack trace is not included
in the production environment.

Set the environment variable `NODE_ENV` to `production`, to run the app in production mode.

When an error is written, the following information is added to the
response:

  • The res.statusCode is set from err.status (or err.statusCode). If
    this value is outside the 4xx or 5xx range, it will be set to 500.
  • The res.statusMessage is set according to the status code.
  • The body will be the HTML of the status code message when in production
    environment, otherwise will be err.stack.
  • Any headers specified in an err.headers object.

If you call next() with an error after you have started writing the
response (for example, if you encounter an error while streaming the
response to the client) the Express default error handler closes the
connection and fails the request.

So when you add a custom error handler, you must delegate to
the default Express error handler, when the headers
have already been sent to the client:

function errorHandler (err, req, res, next) {
  if (res.headersSent) {
    return next(err)
  }
  res.status(500)
  res.render('error', { error: err })
}

Note that the default error handler can get triggered if you call next() with an error
in your code more than once, even if custom error handling middleware is in place.

Writing error handlers

Define error-handling middleware functions in the same way as other middleware functions,
except error-handling functions have four arguments instead of three:
(err, req, res, next). For example:

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

You define error-handling middleware last, after other app.use() and routes calls; for example:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use((err, req, res, next) => {
  // logic
})

Responses from within a middleware function can be in any format, such as an HTML error page, a simple message, or a JSON string.

For organizational (and higher-level framework) purposes, you can define
several error-handling middleware functions, much as you would with
regular middleware functions. For example, to define an error-handler
for requests made by using XHR and those without:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

In this example, the generic logErrors might write request and
error information to stderr, for example:

function logErrors (err, req, res, next) {
  console.error(err.stack)
  next(err)
}

Also in this example, clientErrorHandler is defined as follows; in this case, the error is explicitly passed along to the next one.

Notice that when not calling «next» in an error-handling function, you are responsible for writing (and ending) the response. Otherwise those requests will «hang» and will not be eligible for garbage collection.

function clientErrorHandler (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' })
  } else {
    next(err)
  }
}

Implement the «catch-all» errorHandler function as follows (for example):

function errorHandler (err, req, res, next) {
  res.status(500)
  res.render('error', { error: err })
}

If you have a route handler with multiple callback functions you can use the route parameter to skip to the next route handler. For example:

app.get('/a_route_behind_paywall',
  (req, res, next) => {
    if (!req.user.hasPaid) {
      // continue handling this request
      next('route')
    } else {
      next()
    }
  }, (req, res, next) => {
    PaidContent.find((err, doc) => {
      if (err) return next(err)
      res.json(doc)
    })
  })

In this example, the getPaidContent handler will be skipped but any remaining handlers in app for /a_route_behind_paywall would continue to be executed.

Calls to `next()` and `next(err)` indicate that the current handler is complete and in what state. `next(err)` will skip all remaining handlers in the chain except for those that are set up to handle errors as described above.

May 05, 2018

This page will walk through Angular custom error handler. Angular provides ErrorHandler class for centralized exception handling. We create custom error handler service by implementing ErrorHandler class. It has a method handleError(error) that executes whenever the application throws error anywhere in the application. We need to override it to define our custom error handler. We can use our custom error handler as global error handler. ErrorHandler is created before the providers. So we need Injector to get instance of any service within custom error handler. If our application is using routing then to get URL in custom error handler, we should use Router otherwise we can use Location. We can navigate to a global error page for a user friendly message after handling error in our custom error handler service. Here we will provide complete example to create custom error handler step by step.

Contents
  • Technologies Used
  • Steps to Create Custom Error Handler
  • Using Injector
  • Get URL using Router and Location
  • Using Logger Service
  • Global Error Handler
  • Complete Example
  • Run Application
  • References
  • Download Source Code

Technologies Used

Find the technologies being used in our example.

1. Angular 6.0.0

2. Angular CLI 6.0.0

3. TypeScript 2.7.2

4. Node.js 10.0.0

5. NPM 6.0.0

ErrorHandler

Angular ErrorHandler class is used for centralized exception handling. The default implementation of ErrorHandler prints error messages to browser console. We implement this class to create custom error handler. ErrorHandler has a method handleError(error) that executes whenever the application throws error anywhere in the application. Find the structure of ErrorHandler class from Angular Doc.

class ErrorHandler {
  handleError(error: any): void
} 

To create a custom error handler we need to create a service by implementing ErrorHandler class.

Steps to Create Custom Error Handler

Here we will discuss to create custom error handler using ErrorHandler.

Step-1: Create a service by implementing ErrorHandler and overriding its handleError() method. Here we are creating GlobalErrorHandlerService as custom error handler.

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

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    handleError(error: any) {
      if (error instanceof HttpErrorResponse) {
          //Backend returns unsuccessful response codes such as 404, 500 etc.				  
          console.error('Backend returned status code: ', error.status);
          console.error('Response body:', error.message);          	  
      } else {
          //A client-side or network error occurred.	          
          console.error('An error occurred:', error.message);          
      }     
    }
} 

Step-2: We need to configure our GlobalErrorHandlerService in application module in providers metadata of @NgModule as following.

@NgModule({
  ------
  providers: [
    GlobalErrorHandlerService,
    { provide: ErrorHandler, useClass: GlobalErrorHandlerService },
    -----    
  ]
})
export class AppModule { } 

Now default implementation of ErrorHandler will not be called by application. When an error will be thrown from anywhere in the application, our handleError() method of GlobalErrorHandlerService will execute.

Step-3: Error will be thrown in application automatically or by using throw keyword. In both case GlobalErrorHandlerService will execute. Find the first case.

this.personService.addPerson(person)
     .subscribe(data => {
         console.log(data);
       }
     ); 

Here we are not catching the error, when application will throw error, handleError() method of GlobalErrorHandlerService will execute. Now find the second case.

this.countryService.addCountry(country)
   .subscribe(data => {
       console.log(data);
     },
     err => {
       throw err;
     }
   ); 

If we are catching the error then we need to throw it using throw keyword and then handleError() method of GlobalErrorHandlerService will execute.

Using Injector

ErrorHandler is created before the providers. So we need Injector for dependency injection in our custom error handler class. Injector is imported from @angular/core library. Find the code snippet to use Injector.

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    constructor(private injector: Injector) { }    

    handleError(error: any) {
      let router = this.injector.get(Router);
      console.log('URL: ' + router.url);
      ------
    }
} 

In the above code we are fetching Router using Injector.

Get URL using Router and Location

When an exception is thrown globally, we are interested to know the URL for which exception is thrown. If our application is using routing then to get URL in custom error handler, we should use Router otherwise we can use Location. Find the code snippet to get URL using Router.

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    constructor(private injector: Injector) { }    

    handleError(error: any) {
      let router = this.injector.get(Router);
      console.log('URL: ' + router.url);
      ------
    }
} 

Router is imported from @angular/router library. If our application is not using routing, we can use Location to get URL as following.

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    constructor(private injector: Injector, private location: Location) { }    

    handleError(error: any) {
       console.log(this.location.path());
      ------
    }
} 

We need to configure Location, LocationStrategy and PathLocationStrategy in application module using providers as following.

providers: [
    Location,
    {provide: LocationStrategy, useClass: PathLocationStrategy}        
] 

Location, LocationStrategy and PathLocationStrategy are imported from @angular/common library.

Using Logger Service

We can use a logger service to log error. Suppose we have a LoggerService as following.

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

@Injectable({
    providedIn: 'root'
})
export class LoggerService {
    log(error: any) {
      console.log(error.message);
    }
} 

Now find our custom error handler. We need to get LoggerService instance using Injector.

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    constructor(private injector: Injector) { }    

    handleError(error: any) {
      let router = this.injector.get(Router);
      console.log('URL: ' + router.url);
      
      let loggerService = this.injector.get(LoggerService);
      loggerService.log(error);
    }
} 

Global Error Handler

Creating custom ErrorHandler works as global error handler because our custom ErrorHandler will execute for any error thrown from anywhere in the application. We can redirect from custom error handler service to global error handler page to show a user friendly message. Suppose we have a global error component as following.

import { Component } from '@angular/core';

@Component({
  template: `
        <h2>An error occurred.</h2>
    `
})
export class GlobalErrorComponent {
} 

We need to add path in application routing module as following.

const routes: Routes = [
        ------
	{
	   path: 'error',
	   component: GlobalErrorComponent
	}
]; 

Now find our custom error handler service.

import { Injectable, ErrorHandler, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    constructor(private injector: Injector) { }    

    handleError(error: any) {
      let router = this.injector.get(Router);
      console.log('URL: ' + router.url);
      
      if (error instanceof HttpErrorResponse) {
          //Backend returns unsuccessful response codes such as 404, 500 etc.				  
          console.error('Backend returned status code: ', error.status);
          console.error('Response body:', error.message);          	  
      } else {
          //A client-side or network error occurred.	          
          console.error('An error occurred:', error.message);          
      }     
      router.navigate(['/error']);
    }
} 

Complete Example

Find the project structure.

my-app
|
|--src
|   |
|   |--app 
|   |   |
|   |   |--country
|   |   |     |
|   |   |     |--add-country.component.html
|   |   |     |--add-country.component.ts
|   |   |     |--country.service.ts
|   |   |     |--country.ts
|   |   |
|   |   |--person
|   |   |     |
|   |   |     |--add-person.component.html
|   |   |     |--add-person.component.ts
|   |   |     |--person.service.ts
|   |   |     |--person.ts
|   |   |
|   |   |
|   |   |--global-error-handler.service.ts
|   |   |--global-error.component.ts
|   |   |--page-not-found.component.ts
|   |   |--app-routing.module.ts
|   |   |--app.component.ts
|   |   |--app.module.ts 
|   | 
|   |--main.ts
|   |--index.html
|   |--styles.css
|
|--node_modules
|--package.json 

Now find complete code.

global-error-handler.service.ts

import { Injectable, ErrorHandler, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    constructor(private injector: Injector) { }    

    handleError(error: any) {
      let router = this.injector.get(Router);
      console.log('URL: ' + router.url);
      
      if (error instanceof HttpErrorResponse) {
          //Backend returns unsuccessful response codes such as 404, 500 etc.				  
          console.error('Backend returned status code: ', error.status);
          console.error('Response body:', error.message);          	  
      } else {
          //A client-side or network error occurred.	          
          console.error('An error occurred:', error.message);          
      }     
      router.navigate(['/error']);
    }
} 

global-error.component.ts

import { Component } from '@angular/core';

@Component({
  template: `
        <h2>An error occurred.</h2>
    `
})
export class GlobalErrorComponent {
} 

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AddCountryComponent }  from './country/add-country.component';
import { AddPersonComponent }  from './person/add-person.component';
import { GlobalErrorComponent }  from './global-error.component';
import { PageNotFoundComponent }  from './page-not-found.component';

const routes: Routes = [
	{
	   path: 'country',
	   component: AddCountryComponent
	},	
	{
	   path: 'person',
	   component: AddPersonComponent
	},
	{
	   path: 'error',
	   component: GlobalErrorComponent
	},			
	{
	   path: '',
	   redirectTo: '/country',
	   pathMatch: 'full'
	},
        {
	   path: '**',
	   component: PageNotFoundComponent 
        }	
];
@NgModule({
  imports: [ 
        RouterModule.forRoot(routes) 
  ],
  exports: [ 
        RouterModule 
  ]
})
export class AppRoutingModule{ } 

app.module.ts

import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent }  from './app.component';
import { AddCountryComponent }  from './country/add-country.component';
import { AddPersonComponent }  from './person/add-person.component';
import { GlobalErrorComponent }  from './global-error.component';
import { PageNotFoundComponent }  from './page-not-found.component';
import { AppRoutingModule }  from './app-routing.module';
import { GlobalErrorHandlerService } from './global-error-handler.service';

@NgModule({
  imports: [     
    BrowserModule,
    ReactiveFormsModule,
    HttpClientModule,
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    AddCountryComponent,
    AddPersonComponent,
    GlobalErrorComponent,
    PageNotFoundComponent
  ],
  providers: [
    GlobalErrorHandlerService,
    { provide: ErrorHandler, useClass: GlobalErrorHandlerService }    
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { } 

add-country.component.html

<h3>Add Country</h3>
<form [formGroup]="countryForm" (ngSubmit)="onFormSubmit()">
   <p> Name: <input formControlName="name"> </p>
   <p> Capital: <input formControlName="capital"> </p>
   <p> Currency: <input formControlName="currency"> </p>
   <p> <button>Add</button> </p>
</form> 

add-country.component.ts

import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { CountryService } from './country.service';
import { Country } from './country';

@Component({
    templateUrl: './add-country.component.html'
})
export class AddCountryComponent { 
	constructor(private countryService: CountryService) { }
	countryForm = new FormGroup({
	   name: new FormControl(),
	   capital: new FormControl(),
	   currency: new FormControl()
	});	
	onFormSubmit() {
	   let country = this.countryForm.value;
	   this.countryService.addCountry(country)
	      .subscribe(data => {
		    console.log(data);
		  },
		  err => {
		    throw err;
		  }
	      );
	}
} 

country.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Country } from './country';

@Injectable({
    providedIn: 'root'
})
export class CountryService { 
    url = "/api/countries";
    constructor(private http: HttpClient) { }	
		
    addCountry(country: Country): Observable<Country> {
	return this.http.post<Country>(this.url, country);
    }	
} 

country.ts

export interface Country { 
	name:string;
	capital:string;
	currency:string;
} 

add-person.component.html

<h3>Add Person</h3>
<form [formGroup]="personForm" (ngSubmit)="onFormSubmit()">
   <p> Name: <input formControlName="name"> </p>
   <p> City: <input formControlName="city"> </p>
   <p> Mobile: <input formControlName="mobile"> </p>
   <p> <button>Add</button> </p>
</form> 

add-person.component.ts

import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { PersonService } from './person.service';
import { Person } from './person';

@Component({
    templateUrl: './add-person.component.html'
})
export class AddPersonComponent { 
	constructor(private personService: PersonService) { }
	personForm = new FormGroup({
	   name: new FormControl(),
	   city: new FormControl(),
	   mobile: new FormControl()
	});	
	onFormSubmit() {
	   let person = this.personForm.value;
	   this.personService.addPerson(person)
	      .subscribe(data => {
		   console.log(data);
		}
	      );
	}
} 

person.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Person } from './person';

@Injectable({
  providedIn: 'root'
})
export class PersonService { 
  url = "/api/persons";
  constructor(private http: HttpClient) { }	
		
  addPerson(person: Person): Observable<Person> {
      return this.http.post<Person>(this.url, person);
  }	
} 

person.ts

export interface Person { 
	name:string;
	city:string;
	mobile:string;
} 

page-not-found.component.ts

import { Component } from '@angular/core';
import { Location } from '@angular/common';

@Component({
  template: `<h2>Page Not Found.</h2>
             <div>
                <button (click)="goBack()">Go Back</button>
	     </div>
        `
})
export class PageNotFoundComponent {
	constructor(private location: Location) { }
	goBack(): void {
          this.location.back();
        }
}

app.component.ts

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
	<nav ngClass = "parent-menu">
	  <ul>
		 <li><a routerLink="/country" routerLinkActive="active">Country</a></li>
		 <li><a routerLink="/person" routerLinkActive="active">Person</a></li>
	  </ul> 
	</nav>  
	<div ngClass = "parent-container">	
	  <router-outlet></router-outlet>	
	</div>
  `
})
export class AppComponent { 
} 

styles.css

.parent-menu ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #333;
}
.parent-menu li {
    float: left;
}
.parent-menu li a {
    display: block;
    color: white;
    text-align: center;
    padding: 15px 15px;
    text-decoration: none;
}
.parent-menu li a:hover:not(.active) {
    background-color: #111;
}
.parent-menu .active{
    background-color: #4CAF50;
}
.parent-container {
    padding-left: 10px;
}

Run Application

To run the application, find the steps.

1. Download source code using download link given below on this page.

2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.

3. Run ng serve using command prompt.

4. Access the URL http://localhost:4200 and try to add country or person data. We will get error page.

Find the print screen of the output.

Angular Custom Error Handler

References

Angular Doc: ErrorHandler

Angular Routing and Navigation Example

Download Source Code

Понравилась статья? Поделить с друзьями:
  • Error file write operation failed код ошибки 11
  • Error creating process ошибка
  • Error could not run the item ps2 ошибка
  • Error converting data type nvarchar to bigint ошибка
  • Error compiling script file ошибка inpa