Self-defined Angular Error Handling Framework

Self-defined Angular Error Handling Framework

It is better to use a service to handle Angular errors including errors outside your app(but sent back to app like backend error), network errors, internal errors and so on.

// errors-handler.ts
import { ErrorHandler, Injectable} from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';

import * as StackTraceParser from 'error-stack-parser';
import { ErrorsService } from '../errors-service/errors.service';
import { NotificationService } from '../../services/notification/notification.service';


@Injectable({
  providedIn: 'root'
})
export class ErrorsHandler implements ErrorHandler {

  constructor(
    // 因为 ErrorHandler 的创建在服务的构建引入之前,
    // 所以我们需要通过使用服务的注入器`injector`来手动获取服务
    private injector: Injector
  ){} 

const notificationService = this.injector.get(NotificationService)
const router = this.injector.get(Router)
const errorsService = this.injector.get(ErrorsService);

  handleError(error: Error | HttpErrorResponse) {   
   if (error instanceof HttpErrorResponse) {
      // server side or connection errors
      if (!navigator.onLine) {
        // dealing with network errors
        return notificationService.notify('No network connection');
      } else {
        // Http errors (error.status === 403, 404...)
        //Sending 
         errorsService
           .log(error)
           .subscribe();
        return notificationService.notify(`${error.status} - ${error.message}`);
      }
   } else {
     // client side errors (Angular Error, ReferenceError...)  
     // When error occurred, navigate user to /errors component with detailed error information using route query parameter after error is dealed with errorsService.
          errorsService
          .log(error)
          .subscribe(errorWithContextInfo => {
            router.navigate(['/error'], { queryParams: errorWithContextInfo });   }
  // finally unknown errors:
  console.error('Error occurred: ', error);
  }
}
// errors.module.ts
@NgModule({
  imports: [ ... ],
  declarations: [ ... ],
  providers: [
    {
      provide: ErrorHandler,
      useClass: ErrorsHandler,
    }
  ]
})

Usage example:


@Injectable({
  providedIn: 'root'
})
export class BikesService {
  private readonly apiUrl = environment.apiUrl;
  private bikesUrl = this.apiUrl + '/bikes';

  constructor(
    private http: HttpClient
   ) {}

  /** GET bikes from bikes endpoint */
  getBikes(): Observable<Bike[]> {
    return this.http.get<Bike[]>(this.bikesUrl);
  }
import {  } from '../shared/_services/ErrorsHandler';

export class BikeListComponent implements OnInit {
  // Using Bike Model class
  bikes: Bike[];
  isLoading: Boolean = false;
  public searchText: string;

  constructor(
    private bikeService: BikesService, private errorsHandler: ErrorsHandler) {}

  ngOnInit() {
    // Get bike list
    this.getBikes();
  }

  getBikes(): void {
    this.isLoading = true;
    this.bikeService.getBikes()
      .subscribe(
        response => this.handleResponse(response),
        error => this.errorsHandler(error));
  }

Also, use Http Response Interceptor to retry for 3 times before finally run into ErrorsHandler.

// server-errors.interceptor.ts
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/retry';


@Injectable()
export class ServerErrorsInterceptor implements HttpInterceptor {
  
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 如果请求发生错误,重试3次仍然错误时再进入到ovserver的error情况进行处理。
    return next.handle(request).retry(3);
  }
}

// errors.module.ts
@NgModule({
  imports: [ ... ],
  declarations: [ ... ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ServerErrorsInterceptor,
      multi: true,
    },
  ]
})

Use errorsService to send error records to backend for later analysis using stacktrackjs package.

//errors.service.ts

import { ErrorHandler, Injectable, Injector} from '@angular/core';
import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';

// stacktrackjs是一个十分好用的追踪记录错误的库https://www.stacktracejs.com
import * as StackTraceParser from 'error-stack-parser';

@Injectable()
export class ErrorsService {
	constructor(
    	private injector: Injector,
        private router: Router,
  	) {}

	log(error) {
    	// 将错误打印到控制台
    	console.error(error);
    	// 发送错误到服务器
    	const errorToSend = this.addContextInfo(error);
    	return httpService.post(errorToSend);	
	}

	addContextInfo(error) {
    	// 所有你需要的错误上下文信息详情(它们有的来自其他的`service`服务或者常量,用户服务等)
    	const name = error.name || null;
    	const appId = 'shthppnsApp';
    	const user = 'ShthppnsUser';
    	const time = new Date().getTime();
    	const id = `${appId}-${user}-${time}`;
    	const location = this.injector.get(LocationStrategy);
    	const url = location instanceof PathLocationStrategy ? location.path() : '';
    	const status = error.status || null;
    	const message = error.message || error.toString();
    	const stack = error instanceof HttpErrorResponse ? null : 				StackTraceParser.parse(error);
    	const errorToSend = {name, appId, user, time, id, url, status, message, stack};
    	return errorToSend;
  	}

// 监听 monitoring navigation errors, e.g,  errors emitted by canActivate route guard. 路由守卫认证不通过引发的错误
    this.router
          .events  
          .subscribe((event: Event) => {
            // 重定向到 ErrorComponent 组件
           if (event instanceof NavigationError) {
              if (!navigator.onLine) { return; }
              this.log(event.error)
                      .subscribe((errorWithContext) => {
                        this.router.navigate(['/error'], { queryParams: errorWithContext });
                      });
            }
          });
  }
}

Dealing with 404 errors:

404错误

404错误是十分常见和典型的错误,它会在你请求一个服务器不存在的页面时发生。但在使用单页面应用的项目(SPA)中,页面已经在客户端,不需要通过网络请求获取,网络请求通常只被用来请求用于填充页面的数据

所以,我们应该在什么时候展示一个404错误页呢?那就是在我们请求一个新页面填充所需的数据时,换句话说,当路由改变,新页面开始渲染,但新页面所需的数据并不可用时。这主要分为两种情况:

  • 路由URL改变,但新URL是未定义或者不可用的。
  • 路由守卫解析失败(对应路由所需的数据请求失败)
// errors-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ErrorsComponent } from '../errors-component/errors.component';
const routes: Routes = [
  { path: 'error', component: ErrorsComponent },
  { path: '**', component: ErrorsComponent, data: { error: 404 } },
]
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ErrorRoutingModule { }

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.