Best way API handling in angular
Directory Structure
Below codebase typical Angular project structure for organizing services and interceptors:
This structure keeps core services and interceptors separate from module-specific ones
/src
/app
/core
/services
- auth.service.ts
- error-handler.service.ts
/interceptors
- auth.interceptor.ts
- error.interceptor.ts
/modules
/module-name
/services
- module-specific.service.ts
Service for API Calls
For each module or functionality, we can create a dedicated service. For example, module-specific.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ModuleSpecificService {
constructor(private http: HttpClient) {}
getSomeData(): Observable<any> {
return this.http.get('/api/data').pipe(
catchError(error => {
// Handle or transform error before rethrowing
return throwError(error);
})
);
}
}
Error Handling Service (error-handler.service.ts
)
This service will handle different HTTP errors based on their status codes:
import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class ErrorHandlerService {
public handleError(error: HttpErrorResponse): void {
if (error.error instanceof ErrorEvent) {
// Client-side or network error
console.error('An error occurred:', error.error.message);
} else {
// Backend returned an unsuccessful response code
console.error(`Backend returned code ${error.status}, body was: `, error.error);
this.handleServerError(error);
}
}
private handleServerError(error: HttpErrorResponse): void {
switch (error.status) {
case 401:
// Handle unauthorized error
break;
case 403:
// Handle forbidden error
break;
case 404:
// Handle not found error
break;
case 500:
// Handle internal server error
break;
// Add more cases as needed
default:
// Handle other errors
break;
}
}
}
Error Interceptor (error.interceptor.ts
)
The error interceptor will use the ErrorHandlerService
to handle errors:
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpErrorResponse
} from '@angular/common/http';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { ErrorHandlerService } from '../services/error-handler.service';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private errorHandlerService: ErrorHandlerService) {}
intercept(request: HttpRequest<any>, next: HttpHandler) {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
this.errorHandlerService.handleError(error);
return throwError(error);
})
);
}
}
Implementing the Interceptors
Register the interceptors in app.module.ts
:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './core/interceptors/auth.interceptor';
import { ErrorInterceptor } from './core/interceptors/error.interceptor';
@NgModule({
// ...
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
],
// ...
})
export class AppModule { }
Best Practices we can follow
Modular Services: Keeping services modular and specific to features or modules helps in maintaining a clean codebase and ensures services are easily testable.
Global Error Handling: Using a centralized error handling service ensures consistency in how errors are processed and logged.
Interceptors for Cross-Cutting Concerns: Using interceptors for tasks like adding authentication tokens and handling errors globally helps to keep your service methods clean and focused on their primary responsibilities.
Security Handling: The authentication interceptor adds security tokens to every HTTP request, ensuring secure API communication. Always be cautious with handling tokens and sensitive data.
Error Interceptor: This provides a centralized place to handle all HTTP errors. For example, you can redirect to a login page on a 401 (Unauthorized) error, or show a toast message for a 500 (Server Error).
Implementing these practices leads to a more maintainable, secure, and scalable Angular application.