拦截器在 Angular 中用于拦截 HttpRequest/HttpResponse。我们可以使用拦截器来记录 HttpRequest/HttpResponse 日志,或者在将其传递给服务器之前添加其他 HttpRequest 头部信息,以及更改请求主体或更改从服务器收到的响应的格式等。在本文中,我将解释如何使用多个拦截器以及拦截器的 6 种最常见用法(附示例)。
1. 如何添加多个拦截器? Angular 中可以添加多个拦截器,每个拦截器都有不同的用途。我们可以在一个拦截器中添加所有功能,但这可能会让事情变得混乱。因此,为了使应用程序模块化,我们为不同的目的创建不同的拦截器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { HTTP_INTERCEPTORS } from "@angular/common/http" ;import { AuthInterceptorService } from "./Authentication/auth-interceptor.service" ;import { XML2JsonInterceptorService } from "./ResponseFormat/xml2-json-interceptor.service" ;import { AjaxBusyIdentifierInterceptorService } from "./AjaxBusyIndicator/ajax-busy-identifier-interceptor.service" ;import { RequestTimestampService } from "./RequestTimestamp/request-timestamp.service" ;import { ErrorNotifierService } from "./ErrorNotifier/error-notifier.service" ;import { RetryInterceptorService } from "./RetryInterceptor/retry-interceptor.service" ;export const interceptorProviders = [ { provide : HTTP_INTERCEPTORS , useClass : RequestTimestampService , multi : true , }, { provide : HTTP_INTERCEPTORS , useClass : AjaxBusyIdentifierInterceptorService , multi : true , }, { provide : HTTP_INTERCEPTORS , useClass : AuthInterceptorService , multi : true }, { provide : HTTP_INTERCEPTORS , useClass : XML2JsonInterceptorService , multi : true , }, { provide : HTTP_INTERCEPTORS , useClass : ErrorNotifierService , multi : true }, { provide : HTTP_INTERCEPTORS , useClass : RetryInterceptorService , multi : true , }, ];
HTTP_INTERCEPTORS HttpInterceptor:代表已注册的数组的多 provider 令牌。
const HTTP_INTERCEPTORS: InjectionToken<HttpInterceptor[]>;
provide: 将 useClass 作为一个 HTTP_INTERCEPTORS provider
useClass HttpInterceptor:提及应该添加到数组中的类
multi:true 这是必需的设置,它告诉 angular token 是多提供者。如果将其设置为 false,应用程序将抛出错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import { BrowserModule } from "@angular/platform-browser" ;import { NgModule } from "@angular/core" ;import { AppComponent } from "./app.component" ;import { interceptorProviders } from "./interceptors" ;import { HttpClientModule } from "@angular/common/http" ;import { AjaxBusyIndicatorDirective } from "./AjaxBusyIndicator/ajax-busy-indicator.directive" ;import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;import { ToastrModule } from "ngx-toastr" ;import { RouterModule } from "@angular/router" ;@NgModule ({ declarations : [AppComponent , AjaxBusyIndicatorDirective ], imports : [ BrowserModule , BrowserAnimationsModule , ToastrModule .forRoot (), HttpClientModule , RouterModule , ], providers : [interceptorProviders], bootstrap : [AppComponent ], }) export class AppModule {}
从 interceptors.ts 中导入 interceptorProviders。在装饰器@NgModule providers 中添加 InterceptorProviders 数组.
2. 实现 HttpInterceptor 的基本步骤 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { Injectable } from "@angular/core" ;import { HttpInterceptor , HttpEvent , HttpHandler , HttpRequest , } from "@angular/common/http" ; import { Observable } from "rxjs" ;@Injectable ({ providedIn : "root" , }) export class BasicInterceptorService implements HttpInterceptor { intercept ( req : HttpRequest <any >, next : HttpHandler ): Observable <HttpEvent <any >> { return next.handle (req); } }
BasicInterceptorService.service.ts:
从‘@angular/core’类导入 Injectable 并添加@Injectable({providedId:’root’})装饰器到类中以使其成为 Angular 服务。
从‘@angular/common/http’导入并实现 HttpInterceptor 以使其成为拦截器。
从‘@angular/common/http’中导入 HttpRequest, HttpHandler, HttpEvent 并实现 intercept 函数,继承/实现 HttpInterceptor。
intercept 函数将返回 next.handle(req);。这意味着我们将控制权传递给链中的下一个拦截器(如果有的话)。
您还可以传递自己的可观察对象并短路拦截器链,并忽略前面的拦截器和后端处理程序。例如:
1 2 3 return of ( new HttpResponse <any >({ body : "dummy body" , url : "dummyurl.com" }) );
3. 拦截器示例 3.1. 身份验证/会话拦截器 身份验证拦截器可用于将身份验证标头添加到您的请求中。如果用户未经身份验证,您甚至可以将应用程序路由到登录页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import { Injectable } from "@angular/core" ;import { Observable } from "rxjs" ;import { HttpRequest , HttpHandler , HttpEvent , HttpInterceptor , } from "@angular/common/http" ; import { AuthService } from "./auth.service" ;@Injectable ({ providedIn : "root" , }) export class AuthInterceptorService implements HttpInterceptor { constructor ( public auth : AuthService ) {} intercept ( request : HttpRequest <any >, next : HttpHandler ): Observable <HttpEvent <any >> { if (this .auth .isAuthenticated ) { request = request.clone ({ headers : request.headers .set ( "Authorization" , "Bearer " + this .auth .authToken ), }); } else { } return next.handle (request); } }
AuthInterceptorService.service.ts:
按照基本步骤实现 HttpInterceptor 在构造函数参数中添加 AuthService 依赖项。 使用 this.auth.isAuthenticated 检查用户是否通过身份验证 如果用户通过了身份验证,则将身份验证令牌添加到请求标头。这里我们使用是 request.clone()因为请求是不可变的对象。 如果用户未经身份验证,则将其路由到登录页面。 3.2. 请求格式拦截器 有时我们可能会收到来自 Api 服务器的 XML 格式的响应。我们需要将 XML 数据的格式转换为 Json 格式才能在应用程序中使用它。我们可以使用拦截器来处理此类响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import { NgxXml2 jsonService } from "ngx-xml2json" ;import { Injectable } from "@angular/core" ;import { HttpRequest , HttpResponse , HttpHandler , HttpEvent , HttpInterceptor , } from "@angular/common/http" ; import { Observable } from "rxjs" ;import { map } from "rxjs/operators" ;@Injectable ({ providedIn : "root" , }) export class XML2JsonInterceptorService implements HttpInterceptor { constructor (private xml2jsonService : NgxXml2 jsonService ) {} intercept ( req : HttpRequest <any >, next : HttpHandler ): Observable <HttpEvent <any >> { req = req.clone ({ responseType : "text" }); return next.handle (req).pipe ( map ((event ) => { if ( event instanceof HttpResponse && event.headers .get ("content-type" ).indexOf ("application/xml" ) >= 0 ) { const parser = new DOMParser (); const xml = parser.parseFromString (event.body , "text/xml" ); event = event.clone ({ body : this .xml2jsonService .xmlToJson (xml) }); } else if ( event instanceof HttpResponse && event.headers .get ("content-type" ).indexOf ("application/json" ) >= 0 ) { event = event.clone ({ body : JSON .parse (event.body ) }); } return event; }) ); } }
xml2-json-interceptor.service.ts:
按照基本步骤来实现 HttpInterceptor。
从‘ngx-xml2json’导入 NgxXml2jsonService(XML 到 JSON 转换器服务);
1 2 npm install ngx-xml2json --save
从‘@angular/common/http’ 导入 HttpResponse ;
将预期响应类型更改为 text 而不是 json。我们进行此更改是为了既可以接收 XML 响应 又可以接受 JSON 响应。
将 map 运算符应用于 next.handel(req)。注意:我们使用 map 运算符而不是 tap 因为我们只能使用来读取响应 tap。
在 map 运算符中,如果事件类型为 HttpResponse,并且内容类型为,’application/xml’我们使用以下代码将事件主体从 Xml 转换为 Json:我们使用因为如果不可变。
1 2 3 4 const parser = new DOMParser ();const xml = parser.parseFromString (event.body , ‘text/xml’);event = event.clone ({ body : this .xml2jsonService .xmlToJson (xml) }); event.clone (..)HttpResponse
如果事件类型为 HttpResponse,内容类型为,则’application/json’我们使用将事件主体从文本转换为 Json event = event.clone({body:JSON.parse(event.body)});。我们将主体从文本转换为 JSON,因为我们要求响应类型为文本,但现在我们需要 Json 格式的主体,以便我们可以在应用程序中使用它。 3.3. AJAX动画拦截器 拦截器的最佳示例之一是 ajax 动画拦截器。每当您的 Angular 应用程序发出 AJAX/XHR 请求时,此拦截器将帮助您在应用程序中显示动画。我们主要将加载动画添加到页面的 Header 部分。我们有 3 个部分供此服务使用
拦截器:将会通知指令。 通知服务:包含 API 请求的当前繁忙状态。如果有任何正在进行的 API 请求,busy则传递给的值为 true,否则为false。 指令:将作为 html 属性添加到需要在 api 调用时显示的动画 CSS 或 GIF 中。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import { Injectable } from '@angular/core' ;import { HttpInterceptor , HttpRequest , HttpHandler , HttpEvent } from '@angular/common/http' import { Observable , pipe } from 'rxjs' ;import { finalize, tap } from 'rxjs/operators' ;import { AjaxBusyNotifierService } from './ajax-busy-notifier.service' @Injectable ({ providedIn : 'root' }) export class AjaxBusyIdentifierInterceptorService implements HttpInterceptor { constructor (private abns : AjaxBusyNotifierService ) { } requestCounter = 0 ; intercept (req : HttpRequest <any >, next : HttpHandler ): Observable <HttpEvent <any >> { this .beginRequest (); return next.handle (req).pipe ( finalize (() => { this .endRequest (); }) ); } beginRequest ( ) { this .requestCounter = Math .max (this .requestCounter , 0 ) + 1 ; if (this .requestCounter === 1 ) { this .abns .busy .next (true ); } } endRequest ( ) { this .requestCounter = Math .max (this .requestCounter , 1 ) - 1 ; if (this .requestCounter === 0 ) { this .abns .busy .next (false ); } } }
ajax-busy-identifier-interceptor.service.ts:
按照基本步骤来执行HttpInterceptor。 this.beginRequest();在拦截函数的开头调用。它将增加requestCounter 或设置requestCounter为 1(如果小于或等于 0)。如果requestCounter等于 1,它将传递 true 给abns.busyBehaviourSubject。这将通知 ajax-busy-indicator 指令已发起 ajax 请求。 this.endRequest()在 内调用finalize(()=>{})。如果 requestCounter 大于 1,它将减少 requestCounter,否则将其设置为 0。如果 reequestCounter 等于 0,我们将向abns.busyBehaviourSubject 传递 false。这将通知 ajax-busy-indicator 指令所有 ajax 请求都已完成(收到响应或失败)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { Injectable } from '@angular/core' ;import {BehaviorSubject } from 'rxjs' @Injectable ({ providedIn : 'root' }) export class AjaxBusyNotifierService { busy :BehaviorSubject <boolean >; constructor ( ) { this .busy = new BehaviorSubject <boolean >(false ); this .busy .next (false ); } }
ajax-busy-notifier.service.ts:
AjaxBusyNotifierService包含busy的对象属性BehaviorSubject<boolean>;。BehaviorSubject<boolean>其行为既像可观察对象又像观察者。在拦截器中,当它调用abns.busy.next(true)时,它的行为就像观察者。在指令中,当它调用abns.busy.subscribe((busy)=>{…})时,它的行为就像可观察对象。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import { Directive , Input , OnInit , ElementRef , Renderer2 } from '@angular/core' ;import { interval, Subscription } from 'rxjs' ;import { AjaxBusyNotifierService } from './ajax-busy-notifier.service' ;@Directive ({ selector : '[ajax-busy-indicator]' }) export class AjaxBusyIndicatorDirective implements OnInit { @Input () showDelay : number = 50 ; @Input () hideDelay : number = 1000 ; hideTimer : Subscription ; showTimer : Subscription ; constructor (private el : ElementRef , private renderer : Renderer2 , private abns : AjaxBusyNotifierService ) { } cancelPendingHide ( ) { if (this .hideTimer ) { this .hideTimer .unsubscribe (); delete this .hideTimer ; } } cancelPendingShow ( ) { if (this .showTimer ) { this .showTimer .unsubscribe (); delete this .showTimer ; } } ngOnInit ( ) { this .abns .busy .subscribe (busy => { if (busy) { this .cancelPendingHide (); if (!this .showTimer ) { this .showTimer = interval (this .showDelay ).subscribe (() => { this .renderer .removeClass (this .el .nativeElement , 'inactive' ); this .showTimer .unsubscribe (); this .showTimer = null ; }); } } }); this .abns .busy .subscribe (busy => { if (!busy) { this .cancelPendingShow (); if (!this .hideTimer ) { this .hideTimer = interval (this .hideDelay ).subscribe (() => { this .renderer .addClass (this .el .nativeElement , 'inactive' ); this .hideTimer .unsubscribe (); this .hideTimer = null ; }); } } } ); } }
ajax-busy-indicator.directive.ts:
添加@Directive装饰器AjaxBusyIndicatorDirective类以使其具有指令性。 将选项添加selector:’[ajax-busy-indicator]’到装饰器。这将使其成为属性指令。现在我们可以添加ajax-busy-indicator到任何元素以应用此指令。 声明变量showDelay并hideDelay添加@Input 装饰器使其成为指令的输入属性。 声明showTimer和hideTimer订阅对象,以便我们可以取消订阅显示和隐藏计时器。 cancelPendingHide()将取消订阅 hideTimer 并将cancelPendingShow()取消订阅 showTimer。 在 ngOnInit 中,我们将为abns.busyBehaviourSubject 添加 2 个订阅者。一个用于隐藏内容(加载动画),另一个用于显示指令内的内容。 如果传递给的值为abns.busytrue a) 它将调用cancelPendingHide()取消订阅hideTimer。 b) 它将以 showDelay 作为计时器的周期来启动计时器。 在计时器内,它将从’inactive’指令元素中删除类,然后取消订阅计时器并启动计时器null。 abns.busy类似地,如果为false,我们将进行逆操作,清除动画。 3.4. 通知错误拦截器 如果 AJAX/XHR 请求的响应有错误,可以使用通知错误拦截器来显示错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import { Injectable } from '@angular/core' ;import { HttpInterceptor , HttpRequest , HttpHandler , HttpEvent , HttpErrorResponse } from '@angular/common/http' ;import { Observable } from 'rxjs' ;import { tap, retry, debounceTime } from 'rxjs/operators' ;import { ToastrService } from 'ngx-toastr' ;@Injectable ({ providedIn : 'root' }) export class ErrorNotifierService implements HttpInterceptor { constructor (private toasterService : ToastrService ) { } intercept (request : HttpRequest <any >, next : HttpHandler ): any { return next.handle (request).pipe ( tap ( event => { }, error => { if (error instanceof HttpErrorResponse ) { this .toasterService .error (error.message , error.name , { closeButton : true }); } } )); } }
按照基本步骤来执行HttpInterceptor。 从ngx-toastr导入ToastrService。ToastrService 使用构造函数注入依赖项。constructor(private toasterService: ToastrService){}
在使用 ToasteService 之前,我们需要使用 npm 安装它:npm install ngx-toastr — save
@angular/animations包是默认 toast 所需的依赖项npm install @angular/animations --save
在拦截函数中,使用管道函数将运算tap符应用于next.handle(request):Observable
。点击错误并使用toasterService显示它。 4. 参考文章 Angular HTTP Interceptors : Multiple Interceptors and 6 Code Examples of Interceptors