The basic way to handle errors in Angular is to use Angular's HttpClient service along with RxJS operators throwError and catchError. The HTTP request is made, and it returns the data with a response if anything wrong happens then it returns an error object with an error status code.
One traditional way of handling errors in Angular is to provide an ErrorHandler class. This class can be extended to create your own global error handler. This is also a useful way to handle all errors that occur, but is mostly useful for tracking error logs.
You have some options, depending on your needs. If you want to handle errors on a per-request basis, add a catch
to your request. If you want to add a global solution, use HttpInterceptor
.
Open here the working demo plunker for the solutions below.
In the simplest case, you'll just need to add a .catch()
or a .subscribe()
, like:
import 'rxjs/add/operator/catch'; // don't forget this, or you'll get a runtime error
this.httpClient
.get("data-url")
.catch((err: HttpErrorResponse) => {
// simple logging, but you can do a lot more, see below
console.error('An error occurred:', err.error);
});
// or
this.httpClient
.get("data-url")
.subscribe(
data => console.log('success', data),
error => console.log('oops', error)
);
But there are more details to this, see below.
If you need to handle errors in only one place, you can use catch
and return a default value (or empty response) instead of failing completely. You also don't need the .map
just to cast, you can use a generic function. Source: Angular.io - Getting Error Details.
So, a generic .get()
method, would be like:
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/empty';
import 'rxjs/add/operator/retry'; // don't forget the imports
@Injectable()
export class DataService {
baseUrl = 'http://localhost';
constructor(private httpClient: HttpClient) { }
// notice the <T>, making the method generic
get<T>(url, params): Observable<T> {
return this.httpClient
.get<T>(this.baseUrl + url, {params})
.retry(3) // optionally add the retry
.catch((err: HttpErrorResponse) => {
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', err.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(`Backend returned code ${err.status}, body was: ${err.error}`);
}
// ...optionally return a default fallback value so app can continue (pick one)
// which could be a default value
// return Observable.of<any>({my: "default value..."});
// or simply an empty observable
return Observable.empty<T>();
});
}
}
Handling the error will allow you app to continue even when the service at the URL is in bad condition.
This per-request solution is good mostly when you want to return a specific default response to each method. But if you only care about error displaying (or have a global default response), the better solution is to use an interceptor, as described below.
Run the working demo plunker here.
Once again, Angular.io guide shows:
A major feature of
@angular/common/http
is interception, the ability to declare interceptors which sit in between your application and the backend. When your application makes a request, interceptors transform it before sending it to the server, and the interceptors can transform the response on its way back before your application sees it. This is useful for everything from authentication to logging.
Which, of course, can be used to handle errors in a very simple way (demo plunker here):
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse,
HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/empty';
import 'rxjs/add/operator/retry'; // don't forget the imports
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request)
.catch((err: HttpErrorResponse) => {
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', err.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(`Backend returned code ${err.status}, body was: ${err.error}`);
}
// ...optionally return a default fallback value so app can continue (pick one)
// which could be a default value (which has to be a HttpResponse here)
// return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));
// or simply an empty observable
return Observable.empty<HttpEvent<any>>();
});
}
}
Providing your interceptor: Simply declaring the HttpErrorInterceptor
above doesn't cause your app to use it. You need to wire it up in your app module by providing it as an interceptor, as follows:
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpErrorInterceptor } from './path/http-error.interceptor';
@NgModule({
...
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorInterceptor,
multi: true,
}],
...
})
export class AppModule {}
Note: If you have both an error interceptor and some local error handling, naturally, it is likely that no local error handling will ever be triggered, since the error will always be handled by the interceptor before it reaches the local error handling.
Run the working demo plunker here.
Let me please update the acdcjunior's answer about using HttpInterceptor with the latest RxJs features(v.6).
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpErrorResponse,
HttpHandler,
HttpEvent,
HttpResponse
} from '@angular/common/http';
import { Observable, EMPTY, throwError, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
if (error.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(`Backend returned code ${error.status}, body was: ${error.error}`);
}
// If you want to return a new response:
//return of(new HttpResponse({body: [{name: "Default value..."}]}));
// If you want to return the error on the upper level:
//return throwError(error);
// or just return nothing:
return EMPTY;
})
);
}
}
With the arrival of the HTTPClient
API, not only was the Http
API replaced, but a new one was added, the HttpInterceptor
API.
AFAIK one of its goals is to add default behavior to all the HTTP outgoing requests and incoming responses.
So assumming that you want to add a default error handling behavior, adding .catch()
to all of your possible http.get/post/etc methods is ridiculously hard to maintain.
This could be done in the following way as example using a HttpInterceptor
:
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { _throw } from 'rxjs/observable/throw';
import 'rxjs/add/operator/catch';
/**
* Intercepts the HTTP responses, and in case that an error/exception is thrown, handles it
* and extract the relevant information of it.
*/
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
/**
* Intercepts an outgoing HTTP request, executes it and handles any error that could be triggered in execution.
* @see HttpInterceptor
* @param req the outgoing HTTP request
* @param next a HTTP request handler
*/
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req)
.catch(errorResponse => {
let errMsg: string;
if (errorResponse instanceof HttpErrorResponse) {
const err = errorResponse.message || JSON.stringify(errorResponse.error);
errMsg = `${errorResponse.status} - ${errorResponse.statusText || ''} Details: ${err}`;
} else {
errMsg = errorResponse.message ? errorResponse.message : errorResponse.toString();
}
return _throw(errMsg);
});
}
}
/**
* Provider POJO for the interceptor
*/
export const ErrorInterceptorProvider = {
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
multi: true,
};
// app.module.ts
import { ErrorInterceptorProvider } from 'somewhere/in/your/src/folder';
@NgModule({
...
providers: [
...
ErrorInterceptorProvider,
....
],
...
})
export class AppModule {}
Some extra info for OP: Calling http.get/post/etc without a strong type isn't an optimal use of the API. Your service should look like this:
// These interfaces could be somewhere else in your src folder, not necessarily in your service file
export interface FooPost {
// Define the form of the object in JSON format that your
// expect from the backend on post
}
export interface FooPatch {
// Define the form of the object in JSON format that your
// expect from the backend on patch
}
export interface FooGet {
// Define the form of the object in JSON format that your
// expect from the backend on get
}
@Injectable()
export class DataService {
baseUrl = 'http://localhost'
constructor(
private http: HttpClient) {
}
get(url, params): Observable<FooGet> {
return this.http.get<FooGet>(this.baseUrl + url, params);
}
post(url, body): Observable<FooPost> {
return this.http.post<FooPost>(this.baseUrl + url, body);
}
patch(url, body): Observable<FooPatch> {
return this.http.patch<FooPatch>(this.baseUrl + url, body);
}
}
Returning Promises
from your service methods instead of Observables
is another bad decision.
And an extra piece of advice: if you are using TYPEscript, then start using the type part of it. You lose one of the biggest advantages of the language: to know the type of the value that you are dealing with.
If you want a, in my opinion, good example of an angular service, take a look at the following gist.
For Angular 6+ , .catch doesn't work directly with Observable. You have to use
.pipe(catchError(this.errorHandler))
Below code:
import { IEmployee } from './interfaces/employee';
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class EmployeeService {
private url = '/assets/data/employee.json';
constructor(private http: HttpClient) { }
getEmployees(): Observable<IEmployee[]> {
return this.http.get<IEmployee[]>(this.url)
.pipe(catchError(this.errorHandler)); // catch error
}
/** Error Handling method */
errorHandler(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
}
}
For more details, refer to the Angular Guide for Http
Fairly straightforward (in compared to how it was done with the previous API).
Source from (copy and pasted) the Angular official guide
http
.get<ItemsResponse>('/api/items')
.subscribe(
// Successful responses call the first callback.
data => {...},
// Errors will call this callback instead:
err => {
console.log('Something went wrong!');
}
);
Angular 8 HttpClient Error Handling Service Example
api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Student } from '../model/student';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ApiService {
// API path
base_path = 'http://localhost:3000/students';
constructor(private http: HttpClient) { }
// Http Options
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
}
// Handle API errors
handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
};
// Create a new item
createItem(item): Observable<Student> {
return this.http
.post<Student>(this.base_path, JSON.stringify(item), this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
)
}
........
........
}
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
const PASSENGER_API = 'api/passengers';
getPassengers(): Observable<Passenger[]> {
return this.http
.get<Passenger[]>(PASSENGER_API)
.pipe(catchError((error: HttpErrorResponse) => throwError(error)));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With