How do you globally handle HTTP errors in Angular?
In Angular, the standard way to globally handle HTTP errors is to use an HTTP interceptor.
Instead of writing catchError() separately in every service, an error interceptor can catch errors from all HttpClient requests in one centralized location.
Full Simple Example
1. error.interceptor.ts
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
let errorMessage = 'An unexpected error occurred.';
if (error.status === 0) {
errorMessage = 'Unable to connect to the server.';
}
else if (error.status === 400) {
errorMessage = 'Bad request.';
}
else if (error.status === 401) {
errorMessage = 'Unauthorized. Please login again.';
}
else if (error.status === 403) {
errorMessage = 'You do not have permission to perform this action.';
}
else if (error.status === 404) {
errorMessage = 'Requested resource was not found.';
}
else if (error.status === 500) {
errorMessage = 'Internal server error.';
}
console.error('HTTP Error:', error);
console.error('Error Message:', errorMessage);
return throwError(() => new Error(errorMessage));
})
);
};
2. app.config.ts
Register the interceptor globally:
import { ApplicationConfig } from '@angular/core';
import {
provideHttpClient,
withInterceptors
} from '@angular/common/http';
import { errorInterceptor } from './error.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([
errorInterceptor
])
)
]
};
3. product.service.ts
Your service can now remain clean:
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Product {
id: number;
name: string;
price: number;
}
@Injectable({
providedIn: 'root'
})
export class ProductService {
private http = inject(HttpClient);
private apiUrl =
'https://localhost:7001/api/products';
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>(
this.apiUrl
);
}
}
There is no catchError() here because common HTTP errors are handled centrally.
How does it work?
Suppose Angular calls:
this.http.get(
'https://localhost:7001/api/products'
);
The flow is:
Product Component
↓
Product Service
↓
HttpClient
↓
Error Interceptor
↓
ASP.NET Core Web API
↓
404 / 500 / etc.
↓
Error Interceptor
↓
catchError()
↓
Handle Error
The important part is:
return next(req).pipe(
catchError(...)
);
next(req) sends the request onward and returns an Observable representing the HTTP event/response stream.
If that Observable produces an HTTP error, catchError() intercepts it.
What is HttpErrorResponse?
Angular represents HTTP failures using:
HttpErrorResponse
For example:
catchError((error: HttpErrorResponse) => {
console.log(error.status);
console.log(error.message);
console.log(error.error);
})
Important properties include:
| Property | Meaning |
|---|---|
error.status |
HTTP status code |
error.statusText |
HTTP status text |
error.message |
Angular error message |
error.url |
Request URL |
error.error |
Response body returned by server |
For example:
error.status
could contain:
400
401
403
404
500
Handling different HTTP status codes
A common approach is a switch:
catchError((error: HttpErrorResponse) => {
let message: string;
switch (error.status) {
case 0:
message = 'Server is unavailable.';
break;
case 400:
message = 'Invalid request.';
break;
case 401:
message = 'Please login again.';
break;
case 403:
message = 'Access denied.';
break;
case 404:
message = 'Resource not found.';
break;
case 500:
message = 'Server error.';
break;
default:
message = 'Something went wrong.';
}
return throwError(() => new Error(message));
})
What does status 0 mean?
This is an important interview point.
if (error.status === 0)
usually means Angular did not receive a normal HTTP response.
Possible causes include:
- Server unavailable
- Network connection problem
- DNS/connectivity failure
- CORS-related failure
- Request blocked before a usable HTTP response reached Angular
Therefore:
error.status === 0
does not necessarily mean the server returned HTTP status code 0.
Handling 401 Unauthorized
For authentication failures, you might redirect the user to login:
import { inject } from '@angular/core';
import {
HttpErrorResponse,
HttpInterceptorFn
} from '@angular/common/http';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
const router = inject(Router);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
localStorage.removeItem('accessToken');
router.navigate(['/login']);
}
return throwError(() => error);
})
);
};
In a more complete JWT implementation, 401 may first trigger a refresh-token flow rather than immediately logging the user out.
Using Auth + Error Interceptors Together
A real application commonly has both.
app.config.ts
provideHttpClient(
withInterceptors([
authInterceptor,
errorInterceptor
])
)
Conceptually:
Angular Component
↓
ProductService
↓
HttpClient
↓
Auth Interceptor
│
│ Add JWT
↓
Error Interceptor
↓
ASP.NET Core API
│
│ 401 / 404 / 500
↓
Error Interceptor
│
│ Handle Error
↓
Angular Application
The order of interceptors matters because they form a chain around the request/response pipeline.
Should we still use catchError() inside services?
Sometimes yes.
The interceptor should handle application-wide concerns, while the service/component can handle errors that require business-specific behavior.
For example:
getProduct(id: number) {
return this.http
.get<Product>(`${this.apiUrl}/${id}`)
.pipe(
catchError(error => {
// Product-specific handling
return throwError(() => error);
})
);
}
A useful separation is:
Global Error Interceptor
│
├── 401 → Authentication handling
├── 403 → Access denied
├── 500 → General server error
└── Network failure
↓
Service / Component
│
└── Business-specific handling
So global handling doesn't mean every possible error must be completely handled by the interceptor.
throwError() — Why do we need it?
After intercepting an error, you often want to propagate it to the caller.
return throwError(() => error);
For example:
this.productService.getProducts()
.subscribe({
next: products => {
console.log(products);
},
error: error => {
console.log(error);
}
});
The interceptor handles the global concern, then throwError() allows downstream code to receive the error if it also needs to react.
Key Points
- Use an HTTP interceptor for global HTTP error handling.
- Use RxJS:
catchError()
to catch errors.
- Angular represents HTTP errors using:
HttpErrorResponse
- Check HTTP status using:
error.status
- Typical statuses:
0 → Network/CORS/connectivity-type failure
400 → Bad Request
401 → Unauthorized
403 → Forbidden
404 → Not Found
500 → Internal Server Error
- Register the interceptor globally:
provideHttpClient(
withInterceptors([
errorInterceptor
])
)
- Use
throwError()when the error should continue to the caller:
return throwError(() => error);
- Keep generic/global concerns in the interceptor and business-specific error handling near the relevant service/component.
Interview Questions and Answers
1. How do you globally handle HTTP errors in Angular?
Use an HTTP interceptor and RxJS catchError().
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
console.error(error);
return throwError(() => error);
})
);
2. What is HttpErrorResponse?
HttpErrorResponse is Angular's representation of an error that occurs while processing an HTTP request.
It provides information such as:
error.status
error.message
error.url
error.error
3. What does catchError() do?
catchError() is an RxJS operator that intercepts an error emitted by an Observable and lets you handle, transform, recover from, or rethrow that error.
4. What does throwError() do?
throwError() creates an Observable that emits an error.
It is commonly used after global processing to propagate the error to downstream subscribers.
return throwError(() => error);
5. What should you do when the API returns 401?
Depending on the application's authentication design, you may:
401 Unauthorized
↓
Try Refresh Token
↓
Refresh successful?
/ \
Yes No
↓ ↓
Retry Logout
Request Redirect Login
6. Should all errors be handled inside the interceptor?
No. Handle common application-wide concerns globally and leave business-specific behavior to the relevant service or component.
Interview one-line answer
Global HTTP error handling in Angular is typically implemented with an HTTP interceptor that uses RxJS
catchError()to centrally process errors such as 401, 403, 404, 500, and network failures, while optionally rethrowing the error for feature-specific handling.