Error Handling in Angular: Best Practices and Implementation

Learn how to structure, customize, and display error handling in your Angular project effectively.

Introduction

Error handling is a critical aspect of any Angular application. It ensures that your app can gracefully handle unexpected issues, providing a better user experience and making debugging easier for developers. In this blog, we'll explore how to structure error handling in your Angular project, create customized error handlers, and the best ways to display errors to users.

Structuring Error Handling in Your Angular Project

To maintain a clean and scalable codebase, it's essential to structure your error handling logic properly. Here's a recommended project hierarchy for error handling:

src/
├── app/
│   ├── core/
│   │   ├── errors/
│   │   │   ├── error-handler.service.ts
│   │   │   ├── global-error-handler.ts
│   │   │   ├── app-error.model.ts
│   ├── shared/
│   │   ├── components/
│   │   │   ├── error-display/
│   │   │   │   ├── error-display.component.ts
│   │   │   │   ├── error-display.component.html
│   │   │   │   ├── error-display.component.css
    

This structure ensures that error handling logic is centralized and reusable across your application.

Implementation

Below are examples of how you might implement error-handler.service.ts, global-error-handler.ts, and error.model.ts in an Angular application.

1. error.model.ts


export interface AppError {
  message: string;
  statusCode?: number;
  details?: string;
  timestamp?: Date;
}

export class AppErrorImpl implements AppError {
  message: string;
  statusCode?: number;
  details?: string;
  timestamp?: Date;

  constructor(message: string, statusCode?: number, details?: string) {
    this.message = message;
    this.statusCode = statusCode;
    this.details = details;
    this.timestamp = new Date();
  }
}

2. error-handler.service.ts


import { Injectable } from '@angular/core';
import { AppError, AppErrorImpl } from './error.model';
import { LoggingService } from '../../shared/services/logging.service'; // Assuming you have a logging service

@Injectable({
  providedIn: 'root',
})
export class ErrorHandlerService {
  constructor(private loggingService: LoggingService) {}

  handleError(error: AppError): void {
    // Log the error using a logging service
    this.loggingService.logError(error);

    // You can also add additional error handling logic here
    // For example, sending the error to a remote server
  }

  createError(message: string, statusCode?: number, details?: string): AppError {
    return new AppErrorImpl(message, statusCode, details);
  }
}

3. global-error-handler.ts


import { ErrorHandler, Injectable } from '@angular/core';
import { ErrorHandlerService } from './error-handler.service';
import { AppErrorImpl } from './error.model';

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
  constructor(private errorHandlerService: ErrorHandlerService) {}

  handleError(error: any): void {
    // Convert the error to a known AppError format
    const appError = new AppErrorImpl(
      error.message || 'An unexpected error occurred',
      error.status || 500,
      error.stack
    );

    // Handle the error using the ErrorHandlerService
    this.errorHandlerService.handleError(appError);

    // Optionally, you can rethrow the error or perform other actions
    // throw error;
  }
}

4. Registering the Global Error Handler


import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { GlobalErrorHandler } from './core/errors/global-error-handler';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [
    {
      provide: ErrorHandler,
      useClass: GlobalErrorHandler,
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

5. Example Usage in a Component


import { Component } from '@angular/core';
import { ErrorHandlerService } from '../core/errors/error-handler.service';

@Component({
  selector: 'app-some-component',
  templateUrl: './some-component.component.html',
  styleUrls: ['./some-component.component.css'],
})
export class SomeComponent {
  constructor(private errorHandlerService: ErrorHandlerService) {}

  someMethod() {
    try {
      // Some code that might throw an error
    } catch (error) {
      this.errorHandlerService.handleError(
        this.errorHandlerService.createError('An error occurred in someMethod', 500, error.stack)
      );
    }
  }
}

Displaying Errors to Users

How you display errors to users can significantly impact their experience. Here are some best practices:

  • Use Toast Notifications: Display non-intrusive toast messages for minor errors.
  • Modal Dialogs: Use modal dialogs for critical errors that require user attention.
  • Inline Error Messages: Show validation errors directly in forms.

Here's an example of a reusable error display component:

<div *ngIf="errorMessage" class="error-display">
  <p>{{ errorMessage }}</p>
  <button (click)="clearError()">Dismiss</button>
</div>
    

And its corresponding TypeScript code:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-error-display',
  templateUrl: './error-display.component.html',
  styleUrls: ['./error-display.component.css'],
})
export class ErrorDisplayComponent {
  @Input() errorMessage: string | null = null;

  clearError(): void {
    this.errorMessage = null;
  }
}
    

Conclusion

Proper error handling is essential for building robust Angular applications. By structuring your error handling logic, creating custom error handlers, and displaying errors effectively, you can improve both the user experience and the maintainability of your code. Start implementing these practices in your project today!

Post a Comment

0 Comments