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 { }