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
- When we reference a non-existent variable.
- The value provided is not in the range of allowed values.
- When Interpreting syntactically invalid code
- When a value is not of the expected type
- 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 Error—1 </button> <button (click)=«throwError2()»> Throw Error—2 </button> <router—outlet></router—outlet> |
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
- We can show a simple error page to the user, with a option to retry the operation
- 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.
- 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.
- Use a global error handler to trap all unhandled errors and show a notification to the user.
- 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.
- 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
- 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.
- External
- Internal
- 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:
- Business Rule Violations
- Data Validation Errors
- 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 theconsole
. 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 implementsError
. 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 fromerr.status
(orerr.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 beerr.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 fromerr.status
(orerr.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 beerr.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.
References
Angular Doc: ErrorHandler
Angular Routing and Navigation Example