Home  >  Angular

Angular Custom Error Handler

By Arvind Rai, 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.

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

POSTED BY
ARVIND RAI
ARVIND RAI
FIND MORE TUTORILAS


©2018 concretepage.com | Privacy Policy | Contact Us