HTTP Error Handling In Angular
In this article, we learn about how to handle HTTP errors in Angular. When an HTTP action encounters an error, Angular first encapsulates it in a httpErrorResponse object before returning it. Either in our component class, the data service class, or globally, we catch the httpErrorResponse. The Angular HTTP Interceptor is used to handle global HTTP errors.
HttpErrorResponse
Before providing the errors to our app, the HttpClient gathers them and packages them in a generic HttpErrorResponse. The underlying error object is contained in the HttpErrorResponse's error attribute. Additionally, it gives more information about how the HTTP layer was functioning at the time the problem occurred.
There are two groups of HTTP failures. The error and error answer could both come from the back-end server. A request might not be generated by the client-side code, which would result in an error (ErrorEvent objects).
For a number of reasons, the server might deny the request. The HTTP Status Codes Unauthorized (401), Forbidden (403), Not Found (404), Internal Server Error (500), etc. are returned whenever this occurs. The error response is assigned by Angular to the HttpErrorResponse's error attribute.
The error can also be produced by client-side code. The error could be the result of a network issue, a problem with the HTTP request itself, or an exception produced by an RxJS operator. JavaScript ErrorEvent objects are generated by these errors. The error property of the HttpErrorResponse receives the ErrorEvent object from Angular.
The HTTP Module always returns the generic HttpErrorResponse in both scenarios. To determine the type of mistake, we will examine the error attribute.
Catching Errors in HTTP Request
The three locations where we can catch HTTP errors are as follows.
- Component
- Service
- Globally
Catch Errors in Component
Please see our Angular HTTP Get Request article. In order to obtain the list of Repositories, we built a GitHubService and sent a GET call to the GitHub API. The getRepos() method from the service is as follows. To cause an error, we purposefully altered the URL to be (uersY).
getRepos(userName: string): Observable<any> { return this.http.get(this.baseURL + 'usersY/' + userName + '/repos') }
In the component class, we subscribe to the httpClient.get method.
public getRepos() { this.loading = true; this.errorMessage = ""; this.githubService.getReposCatchError(this.userName) .subscribe( (response) => { //Next callback console.log('response received') this.repos = response; }, (error) => { //Error callback console.error('error caught in component') this.errorMessage = error; this.loading = false; //throw error; //You can also throw the error to a global error handler } ) }
There are three callback arguments in the subscribe method.
.subscribe(success, error, completed);
(error) => { //Error callback console.error('error caught in component') this.errorMessage = error; this.loading = false; }
Catch Errors in Service
getRepos(userName: string): Observable<repos[]> { return this.http.get<repos[]>(this.baseURL + 'usersY/' + userName + '/repos') .pipe( catchError((err) => { console.log('error caught in service') console.error(err); //Handle the error here return throwError(err); //Rethrow it back to component }) ) }
Catch error globally using HTTP Interceptor
- You do not have permission to use the API Service,
- You have permission, but you are not allowed to access this resource.
- The API End Point is incorrect or nonexistent.
- network error
- Server down
import {Injectable} from "@angular/core"; import {HttpEvent, HttpHandler, HttpInterceptor,HttpRequest,HttpResponse,HttpErrorResponse} from '@angular/common/http'; import {Observable, of, throwError} from "rxjs"; import {catchError, map} from 'rxjs/operators'; @Injectable() export class GlobalHttpInterceptorService implements HttpInterceptor { constructor(public router: Router) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req).pipe( catchError((error) => { console.log('error is intercept') console.error(error); return throwError(error.message); }) ) } }
providers: [ GitHubService, { provide: HTTP_INTERCEPTORS, useClass: GlobalHttpInterceptorService, multi: true } ]
Errors Handling in HTTP
if (error instanceof HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.error("Error Event"); } else { console.log(`error status : ${error.status} ${error.statusText}`); switch (error.status) { case 401: //login this.router.navigateByUrl("/login"); break; case 403: //forbidden this.router.navigateByUrl("/unauthorized"); break; } } } else { console.error("some thing else happened"); } return throwError(error);
return throwError(error);
.subscribe( (response) => { this.repos = response; }, (error) => { //Handle the error here //If not handled, then throw it throw error; } )
HTTP Error handling example
<h1 class="heading"><strong>Angular HTTP</strong>Error Example</h1> <div class="form-group"> <label for="userName">GitHub User Name</label> <input type="text" class="form-control" name="userName" [(ngModel)]="userName"> </div> <div class="form-group"> <button type="button" (click)="getRepos()">Get Repos</button> </div> <div *ngIf="loading">loading...</div> <div *ngIf="errorMessage" class="alert alert-warning"> <strong>Warning!</strong> {{errorMessage | json}} </div> <table class='table'> <thead> <tr> <th>ID</th> <th>Name</th> <th>HTML Url</th> <th>description</th> </tr> </thead> <tbody> <tr *ngFor="let repo of repos;"> <td>{{repo.id}}</td> <td>{{repo.name}}</td> <td>{{repo.html_url}}</td> <td>{{repo.description}}</td> </tr> </tbody> </table> - <pre>{{repos | json}}</pre>
Add the following code under the app.component.ts:
import { Component } from '@angular/core'; import { GitHubService } from './github.service'; import { repos } from './repos'; @Component({ selector: 'app-root', templateUrl: './app.component.html', }) export class AppComponent { userName: string = "TheCodePoints" repos: repos[]; loading: boolean = false; errorMessage; constructor(private githubService: GitHubService) { } public getRepos() { this.loading = true; this.errorMessage = ""; this.githubService.getReposCatchError(this.userName) .subscribe( (response) => { //Next callback console.log('response received') this.repos = response; }, (error) => { //Error callback console.error('error caught in component') this.errorMessage = error; this.loading = false; throw error; } ) } }
Add the following code under the github.service.ts:
import { Injectable } from '@angular/core'; import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; import { repos } from './repos'; @Injectable( {providedIn:'root'}) export class GitHubService { baseURL: string = "https://api.github.com/"; constructor(private http: HttpClient) { } //Any Data Type getRepos(userName: string): Observable<any> { return this.http.get(this.baseURL + 'usersY/' + userName + '/repos') } //With catchError getReposCatchError(userName: string): Observable<repos[]> { return this.http.get<repos[]>(this.baseURL + 'usersY/' + userName + '/repos') .pipe( catchError((err) => { console.log('error caught in service') console.error(err); return throwError(err); }) ) } }
Add the following code under the global-http-Interceptor.service.ts:
import { Injectable } from "@angular/core"; import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpErrorResponse } from '@angular/common/http'; import { Observable, of, throwError } from "rxjs"; import { catchError, map } from 'rxjs/operators'; import { Router } from '@angular/router'; @Injectable() export class GlobalHttpInterceptorService implements HttpInterceptor { constructor(public router: Router) { } //1. No Errors intercept1(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req).pipe( catchError((error) => { console.log('error in intercept') console.error(error); return throwError(error.message); }) ) } //2. Sending an Invalid Token will generate error intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token: string = 'invald token'; req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) }); return next.handle(req).pipe( catchError((error) => { console.log('error in intercept') console.error(error); return throwError(error.message); }) ) } intercept3(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token: string = 'invald token'; req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) }); return next.handle(req).pipe( catchError((error) => { let handled: boolean = false; console.error(error); if (error instanceof HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.error("Error Event"); } else { console.log(`error status : ${error.status} ${error.statusText}`); switch (error.status) { case 401: //login this.router.navigateByUrl("/login"); console.log(`redirect to login`); handled = true; break; case 403: //forbidden this.router.navigateByUrl("/login"); console.log(`redirect to login`); handled = true; break; } } } else { console.error("Other Errors"); } if (handled) { console.log('return back '); return of(error); } else { console.log('throw error back to to the subscriber'); return throwError(error); } }) ) } }
Add the following code under the global-error-handler.service.ts:
import { ErrorHandler, Injectable, Injector } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { throwError } from 'rxjs'; @Injectable() export class GlobalErrorHandlerService implements ErrorHandler { constructor() { } handleError(error: Error | HttpErrorResponse) { console.log('GlobalErrorHandlerService') console.error(error); } }
Add the following code under the app.module.ts:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule ,ErrorHandler } from '@angular/core'; import { HttpClientModule,HTTP_INTERCEPTORS} from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { GlobalHttpInterceptorService} from './global-http-Interceptor.service'; import { AppRoutingModule } from './app-routing.module'; import { GlobalErrorHandlerService } from './global-error-handler.service'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule, FormsModule, AppRoutingModule ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: GlobalHttpInterceptorService, multi: true }, { provide: ErrorHandler, useClass:GlobalErrorHandlerService} ], bootstrap: [AppComponent] }) export class AppModule { }
Add the following code under the app-routing.module.ts:
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }