Graceful Error Handling with Error Boundary Components in Angular

Introduction

Error handling is an essential aspect of building robust Angular applications. When errors occur within components, they can propagate and disrupt the application's flow, leading to broken UI and a poor user experience. In this article, we will explore how to implement error boundary components in Angular to gracefully handle errors, display fallback UI, and ensure that critical components like side navigation bars always load, even in the presence of errors.

Understanding Error Boundary Components

In this section, we introduce the concept of error boundary components inspired by React's error boundaries. We explain their purpose, which is to catch errors that occur within their child components, preventing them from propagating and breaking the application.

Implementing the ErrorBoundaryComponent

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

@Component({
  selector: 'app-error-boundary',
  template: `
    <ng-container *ngIf="!hasError">
      <ng-content></ng-content>
    </ng-container>
    <ng-container *ngIf="hasError">
      <ng-content select="[fallback]"></ng-content>
      <div class="error-message">
        <h2>An error occurred</h2>
        <p>{{ errorMessage }}</p>
      </div>
    </ng-container>
  `,
  styles: [`
    .error-message {
      color: red;
    }
  `]
})
export class ErrorBoundaryComponent {
  public hasError = false;
  public errorMessage = '';

  constructor(private errorHandler: ErrorHandler) {}

  handleError(error: any) {
    this.hasError = true;
    this.errorMessage = error.message || 'Something went wrong.';
    // You can log the error or perform any necessary actions here
    console.error(error);
  }
}

In this code snippet, we define the ErrorBoundaryComponent as an Angular component. The component selector is app-error-boundary, but you can adjust it to suit your project's naming conventions.

The component template consists of two ng-container blocks. The first ng-container is rendered when there are no errors (!hasError), and it uses the ng-content directive to display the content of the child components normally.

The second ng-container is rendered when an error occurs (hasError is true). It first selects any content marked with the [fallback] attribute using ng-content with the select attribute. This allows you to provide a fallback UI within your child components using the [fallback] attribute.

After displaying the fallback UI (if any), the template shows an error message with a <h2> heading and an <p> element containing the errorMessage.

The ErrorBoundaryComponent class contains two properties: hasError and errorMessage. The hasError property is a boolean flag that indicates whether an error has occurred. The errorMessage property stores the error message to display.

The constructor of the ErrorBoundaryComponent injects an instance of the ErrorHandler service. We'll implement the custom ErrorHandler class in the next section.

The handleError< method is responsible for setting the hasError flag to true, extracting the error message from the error object (if available), and performing any necessary error logging or actions.

Creating a Custom ErrorHandler

import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { ErrorBoundaryComponent } from './error-boundary.component';

@Injectable()
export class CustomErrorHandler implements ErrorHandler {
  private injector: Injector;

  constructor(injector: Injector) {
    this.injector = injector;
  }

  handleError(error: any): void {
    const errorBoundary = this.injector.get(ErrorBoundaryComponent);
    errorBoundary.handleError(error);
  }
}

In this code snippet, we define the CustomErrorHandler class that implements the ErrorHandler interface provided by Angular. The CustomErrorHandler is annotated with the @Injectable() decorator to make it injectable as a service.

The CustomErrorHandler constructor injects an instance of the Injector service. The Injector is used to obtain an instance of the ErrorBoundaryComponent within the handleError method.

The handleError method receives the error object and delegates the error-handling responsibility to the ErrorBoundaryComponent by calling its handleError method. This allows the ErrorBoundaryComponent to set the appropriate flags and display the error message.

Providing the Custom ErrorHandler

To provide the custom CustomErrorHandler in the app module and replace the default ErrorHandler, follow the code snippet below:

import { NgModule, ErrorHandler } from '@angular/core';
import { CustomErrorHandler } from './custom-error-handler';
import { ErrorBoundaryComponent } from './error-boundary.component';

@NgModule({
  declarations: [
    ErrorBoundaryComponent
  ],
  providers: [
    { provide: ErrorHandler, useClass: CustomErrorHandler }
  ],
  // Other module configurations
})
export class AppModule { }

In this code snippet, we import the CustomErrorHandler and ErrorBoundaryComponent and declare the ErrorBoundaryComponent in the declarations array of the app module.

Next, we provide the CustomErrorHandler as a replacement for the default ErrorHandler by adding it to the provider's array of the app module. We use the { provide: ErrorHandler, useClass: CustomErrorHandler } syntax to specify that whenever an ErrorHandler is requested, Angular should use the

Make sure to import and include the AppModule in your application's bootstrap process.

Using the ErrorBoundaryComponent

<app-error-boundary>
  <!-- Your component's content -->
</app-error-boundary>

By wrapping your components with the app-error-boundary selector, you enable the error boundary functionality provided by the ErrorBoundaryComponent.

Additionally, you can add fallback UI and loading indicators within your components' content. To display a fallback UI, mark the fallback content with the [fallback] attribute like this.

<app-error-boundary>
  <ng-container fallback>
    <!-- Fallback UI content here -->
  </ng-container>

  <!-- Your component's content -->
</app-error-boundary>

You can also add a loading indicator within the ErrorBoundaryComponent template. The loading indicator is displayed when the isLoading property is set to true. You can modify the component and template to handle the loading state based on your application's requirements.

Conclusion

In this article, we explored the concept of error boundary components in Angular and learned how to implement them to gracefully handle errors, display fallback UI, and ensure critical components always load, even in the presence of errors. We created the ErrorBoundaryComponent to encapsulate error handling logic, implemented a custom ErrorHandler to capture and delegate errors to the error boundary component, and provided the custom ErrorHandler in the app module. We also discussed how to use the ErrorBoundaryComponent by wrapping components prone to errors and adding fallback UI and loading indicators.

By utilizing error boundary components, you can enhance the error-handling capabilities of your Angular application, improving the user experience and preventing errors from propagating and breaking the UI. Incorporate this pattern into your projects to create more robust and user-friendly Angular applications.