This is the 20th day of my participation in Gwen Challenge. For details, see Gwen Challenge.

Error handling is a common requirement when writing code. The logic of handling exceptions is often to avoid crashes. This article will briefly describe how Angular handles exceptions.

What are the presents

Angualr is an open source Web front-end framework from Google. It was created by Misko Hevery et al in 2009 and later acquired by Google. Is an excellent front-end JS framework, has been used in a number of Google products.

AngularJS is declarative programming where users can develop based on business logic. The framework is based on HTML content filling and two-way data binding to complete automatic data synchronization mechanism. Finally, AngularJS enhanced DOM manipulation enhances testability.

try/catch

The most familiar approach is to add a try/catch block to your code, where an error in a try is caught and the script continues to execute. However, as the application grows in size, this approach becomes unmanageable.

ErrorHandler

Angular provides a default ErrorHandler that prints error messages to the console, so you can intercept this default behavior to add custom handling logic. Try writing an error handling class:

import { ErrorHandler, Injectable } from "@angular/core"; import { HttpErrorResponse } from "@angular/common/http"; @Injectable() export class ErrorsHandler implements ErrorHandler { handleError(error: Error | HttpErrorResponse) { if (! navigator.onLine) { console.error("Browser Offline!" ); } else { if (error instanceof HttpErrorResponse) { if (! navigator.onLine) { console.error("Browser Offline!" ); } else { // Handle Http Error (4xx, 5xx, ect.) console.error("Http Error!" ); } } else { // Handle Client Error (Angular Error, ReferenceError...) console.error("Client Error!" ); } console.error(error); }}}Copy the code

Usually create a shared directory under your app, shared, and put this file in the providers folder

Now, we need to change the default behavior of our application to use our custom class instead of ErrorHandler. Modify the app.module.ts file to import ErrorHandler from @Angular /core and add providers to the @NgModule module as follows:

import { NgModule, ErrorHandler } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";

// Providers
import { ErrorsHandler } from "./shared/providers/error-handler";

import { AppComponent } from "./app.component";

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

Copy the code

HttpInterceptor

HttpInterceptor provides a way to intercept HTTP requests/responses so that they can be processed before being passed. For example, an HTTP request can be retried several times before an error is thrown. This way, timeouts can be handled gracefully without throwing errors.

You can also check the status of an error before throwing it, and with the interceptor, you can check the 401 status error code to redirect the user to the login page.

import { Injectable } from "@angular/core"; import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse } from "@angular/common/http"; import { Observable, throwError } from "rxjs"; import { retry, catchError } from "rxjs/operators"; @Injectable() export class HttpsInterceptor implements HttpInterceptor { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request).pipe( retry(1), catchError((error: HttpErrorResponse) => {if (error.status === 401) {else {return throwError(error); }})); }}Copy the code

It also needs to be added to app.module.ts

import { NgModule, ErrorHandler } from "@angular/core";
import { HTTP_INTERCEPTORS } from "@angular/common/http";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";

// Providers
import { ErrorsHandler } from "./shared/providers/error-handler";
import { HttpsInterceptor } from "./shared/providers/http-interceptor";

import { AppComponent } from "./app.component";

@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [AppComponent],
  providers: [
    { provide: ErrorHandler, useClass: ErrorsHandler },
    { provide: HTTP_INTERCEPTORS, useClass: HttpsInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
Copy the code

Multiple providers are used to create extensible services where the system comes with some default providers and can register other providers. A combination of the default provider and other providers is used to drive the behavior of the system.

Notifications

Printing error logs on the console is developer friendly, but users need a more user-friendly way to tell when errors occur from the GUI. Depending on the error type, two components are recommended: Snackbar and Dialog

  • Snackbar: Recommended for simple prompts, such as forms missing required fields or notifying users of predictable errors (invalid E-mail, user names too long, etc.).

  • Dialog: Recommended when there are unknown server side or client side errors; This way, more descriptions and even call-to-action can be displayed, such as allowing users to enter their E-mail to track errors.

Add a service to the shared folder to handle all notifications, create a new “services” folder, and create a file: notification.service.ts.

import { Injectable } from "@angular/core"; import { MatSnackBar } from "@angular/material/snack-bar"; @Injectable({ providedIn: "root" }) export class NotificationService { constructor(public snackBar: MatSnackBar) {} showError(message: string) { this.snackBar.open(message, "Close", { panelClass: ["error"] }); }}Copy the code

Update error-handler.ts to add NotificationService:

import { ErrorHandler, Injectable, Injector } from "@angular/core"; import { HttpErrorResponse } from "@angular/common/http"; // Services import { NotificationService } from ".. /services/notification.service"; @Injectable() export class ErrorsHandler implements ErrorHandler {//Error handling Injector Service Constructor (Private Injector: Injector) {} handleError(error: Error | HttpErrorResponse) { const notifier = this.injector.get(NotificationService); if (! navigator.onLine) { //console.error("Browser Offline!" ); notifier.showError("Browser Offline!" ); } else { if (error instanceof HttpErrorResponse) { if (! navigator.onLine) { //console.error("Browser Offline!" ); notifier.showError(error.message); } else { // Handle Http Error (4xx, 5xx, ect.) // console.error("Http Error!" ); notifier.showError("Http Error: " + error.message); } } else { // Handle Client Error (Angular Error, ReferenceError...) // console.error("Client Error!" ); notifier.showError(error.message); } console.error(error); }}}Copy the code

If you throw an error in a component, you can see a nice snackbar message:

Logging and error tracing

Of course, you can’t expect users to report every bug, and once deployed to production, you can’t see any console logs. Therefore, back-end services that can log errors with custom logic write to the database or use existing solutions such as Rollbar, Sentry, and Bugsnag are required.

Next create a simple error tracing service, creating logging.service.ts:

import { Injectable } from "@angular/core"; import { HttpErrorResponse } from "@angular/common/http"; @Injectable({ providedIn: "root" }) export class LoggingService { constructor() {} logError(error: Error | HttpErrorResponse) { // This will be replaced with logging to either Rollbar, Sentry, Bugsnag, ect. if (error instanceof HttpErrorResponse) { console.error(error); } else { console.error(error); }}}Copy the code

Add the service to error-handler.ts:

import { ErrorHandler, Injectable, Injector } from "@angular/core"; import { HttpErrorResponse } from "@angular/common/http"; // Services import { NotificationService } from ".. /services/notification.service"; import { LoggingService } from ".. /services/logging.service"; @Injectable() export class ErrorsHandler implements ErrorHandler {//Error handling Injector Service Constructor (Private Injector: Injector) {} handleError(error: Error | HttpErrorResponse) { const notifier = this.injector.get(NotificationService); const logger = this.injector.get(LoggingService); if (! navigator.onLine) { //console.error("Browser Offline!" ); notifier.showError("Browser Offline!" ); } else { if (error instanceof HttpErrorResponse) { if (! navigator.onLine) { //console.error("Browser Offline!" ); notifier.showError(error.message); } else { // Handle Http Error (4xx, 5xx, ect.) // console.error("Http Error!" ); notifier.showError("Http Error: " + error.message); } } else { // Handle Client Error (Angular Error, ReferenceError...) // console.error("Client Error!" ); notifier.showError(error.message); } // console.error(error); logger.logError(error); }}}Copy the code

At this point, the entire error handling mechanism has been introduced, and is basically similar to how other framework or language development projects are handled.