Lejdi Prifti

0 %
Lejdi Prifti
Software Engineer
DevOps Engineer
ML Practitioner
  • Residence:
    Albania
  • City:
    Tirana
  • Email:
    info@lejdiprifti.com
Spring
AWS & Cloud
Angular
Team Player
Coordination & Leadership
Time Management
Docker & Kubernetes
ReactJs
JavaScript
Python
  • Java, JavaScript, Python
  • AWS, Kubernetes, Azure
  • Bootstrap, Materialize
  • Css, Sass, Less
  • Blockchain, Ethereum, Solidity
  • React, React Native, Flutter
  • GIT knowledge
  • Machine Learning, Deep Learning
0

No products in the basket.

Building a global HTTP error handler in Angular 17

14. July 2024

Introduction

One of the key principles of writing clean code is DRY (Don’t Repeat Yourself). To avoid repetitive lines of code that manage errors from HTTP API calls, we will learn how to implement a global HTTP error handler in Angular 17.

Table of Contents

Problem definition

In the project I’m developing for a client, I use toasts to inform users about potential issues when the backend API is called. For every known exception thrown, the backend responds with an object containing a code and a message. This code maps to the frontend, and a translation service displays it in different languages. If you want to learn how to integrate a translation service into your Angular 17 application, have a look at this easy-to-follow tutorial.

For instance, the JSON object below demonstrates a scenario where a user requests a scoreboard that cannot be found.

				
					{"code":"scrbrdntfnd","message":"scoreboard not found"}
				
			

For every call that I make to the backend, I also handle the error that could potentially happen. However, this adds a lot of repetitive code that is not okay. 

As shown in the code snippet below, the share method of the scoreboardService service makes a POST request to the Spring Boot application and returns an Observable. By subscribing to this Observable, I handle responses based on the request status, whether successful or not.

				
					  openShareDialog(): void {
    this.scoreboardService.share(this.scoreboardId).subscribe({
      next: (response) => {
        // makes some business logic
      },
      error: (response) => {
        this.messageService.add({
          severity: 'error',
          summary: this.translateService.instant(`error.status.${response.status}.label`),
          detail: this.translateService.instant(`error.${response.error.code}`)
        })
      }
    })
  }
				
			

I noticed I was repeating the same task frequently and realized there must be a more efficient approach. Indeed, there is! I’ll show you how to build global HTTP error handler in Angular 17 to streamline your workflow and eliminate the need for repetitive code.

Configuring the error handling service

Before we start configuring the global HTTP error interceptor, we firstly must create a service that I have named ErrorHandlingService.

It’s decorated with @Injectable({ providedIn: 'root' }), ensuring it’s available across the entire application as a singleton service. The service depends on MessageService from PrimeNG for displaying error messages. It also optionally depends on TranslateService from @ngx-translate/core for localization.

The setTranslateService method allows injecting TranslateService dynamically to enable error message translation. Having a lazy-loading approach is crucial to prevent circular dependency errors caused by the TranslateService, which utilizes HTTP for loading language packages.

Finally, the handleHttpError method is pivotal in managing HTTP errors. When an HttpErrorResponse occurs, it retrieves a localized summary and detail message using TranslateService. The summary corresponds to the HTTP status code label fetched from translations, and detail captures a custom error code message from translations if available. These localized messages are then displayed using MessageService from PrimeNG, configured with severity set to ‘error’ for clear indication of error conditions to users.

				
					import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { MessageService } from 'primeng/api';

@Injectable({
  providedIn: 'root'
})
export class ErrorHandlingService {
  
  private translateService: TranslateService | undefined;

  constructor(private messageService: MessageService) { }

  setTranslateService(translateService: TranslateService): void {
    this.translateService = translateService;
  }

  handleHttpError(error: HttpErrorResponse): void {
    const summary = this.translateService?.instant(`error.status.${error.status}.label`);
    const detail = this.translateService?.instant(`error.${error.error?.code}`);
    this.messageService.add({ severity: 'error', summary, detail });
  }
}
				
			

Configuring the global HTTP error interceptor

Now, it is time to configure the HttpErrorInterceptor !

The HttpErrorInterceptor in Angular intercepts HTTP requests and responses to handle errors globally across the application. It implements the HttpInterceptor interface to intercept HTTP requests and responses, providing centralized error handling.

In the intercept method, the interceptor captures HttpRequest and HttpHandler parameters. It uses next.handle(req) to forward the request to the next interceptor or the backend server. The pipe operator then catches any errors that occur during the request/response cycle using catchError. When an HttpErrorResponse is caught, errorHandlingService.handleHttpError(error) is called to delegate error handling to ErrorHandlingService that we developed in the previous section. 

				
					import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, catchError, throwError } from "rxjs";
import { ErrorHandlingService } from "../services/error.handling.service";

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {

    constructor(private errorHandlingService: ErrorHandlingService) { }

    intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
        return next.handle(req).pipe(
            catchError((error: HttpErrorResponse) => {
                this.errorHandlingService.handleHttpError(error);
                return throwError(() => error);
            }))
    }
}
				
			

In the app.config.ts, we need to add the HttpErrorInterceptor to the list of providers. The HTTP_INTERCEPTORS provider configures the HttpErrorInterceptor to intercept HTTP requests and responses. 

				
					export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    {
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      multi: true,
      deps: [KeycloakService]
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpErrorInterceptor,
      multi: true
    },
    // ...
    ]
    // ...
}
				
			

We are not done yet! Do you remember that the TranslateService is configured to be lazily loaded? We need to inject it in the ErrorHandingService when it becomes available in the AppComponent (app.component.ts) .

				
					@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, ToastModule, TranslateModule, ConfirmDialogModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {

  constructor(translateService: TranslateService, errorHandlingService: ErrorHandlingService) {
    translateService.use('en');
    errorHandlingService.setTranslateService(translateService);
  }

}
				
			

Conclusion

And we are done! Now I can clean up my previous method and it looks great.

				
					  openShareDialog(): void {
    this.scoreboardService.share(this.scoreboardId).subscribe({
      next: (response) => {
        // makes some business logic
      }
    })
  }
				
			

Hope you find it helpful! If you want to learn more about Angular, check out this list of articles.

Happy coding and God bless you!

Posted in AngularTags:
Write a comment