Angular HTTP 拦截器:配置多个HTTP拦截器和 4 个拦截器代码示例

拦截器在 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:

  1. 从‘@angular/core’类导入 Injectable 并添加@Injectable({providedId:’root’})装饰器到类中以使其成为 Angular 服务。

  2. 从‘@angular/common/http’导入并实现 HttpInterceptor 以使其成为拦截器。

  3. 从‘@angular/common/http’中导入 HttpRequest, HttpHandler, HttpEvent 并实现 intercept 函数,继承/实现 HttpInterceptor。

  4. intercept 函数将返回 next.handle(req);。这意味着我们将控制权传递给链中的下一个拦截器(如果有的话)。

  5. 您还可以传递自己的可观察对象并短路拦截器链,并忽略前面的拦截器和后端处理程序。例如:

    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 //,public router: Router
) {}

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 {
// this.router.navigate(['LoginPage']);
}
return next.handle(request);
}
}

AuthInterceptorService.service.ts:

  1. 按照基本步骤实现 HttpInterceptor
  2. 在构造函数参数中添加 AuthService 依赖项。
  3. 使用 this.auth.isAuthenticated 检查用户是否通过身份验证
  4. 如果用户通过了身份验证,则将身份验证令牌添加到请求标头。这里我们使用是 request.clone()因为请求是不可变的对象。
  5. 如果用户未经身份验证,则将其路由到登录页面。

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 { NgxXml2jsonService } 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: NgxXml2jsonService) {}

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:

  1. 按照基本步骤来实现 HttpInterceptor。

  2. 从‘ngx-xml2json’导入 NgxXml2jsonService(XML 到 JSON 转换器服务);

    1
    2
    # 您需要在项目中使用该包之前安装它。
    npm install ngx-xml2json --save
  3. 从‘@angular/common/http’ 导入 HttpResponse ;

  4. 将预期响应类型更改为 text 而不是 json。我们进行此更改是为了既可以接收 XML 响应 又可以接受 JSON 响应。

  5. 将 map 运算符应用于 next.handel(req)。注意:我们使用 map 运算符而不是 tap 因为我们只能使用来读取响应 tap。

  6. 在 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
  1. 如果事件类型为 HttpResponse,内容类型为,则’application/json’我们使用将事件主体从文本转换为 Json event = event.clone({body:JSON.parse(event.body)});。我们将主体从文本转换为 JSON,因为我们要求响应类型为文本,但现在我们需要 Json 格式的主体,以便我们可以在应用程序中使用它。

3.3. AJAX动画拦截器

拦截器的最佳示例之一是 ajax 动画拦截器。每当您的 Angular 应用程序发出 AJAX/XHR 请求时,此拦截器将帮助您在应用程序中显示动画。我们主要将加载动画添加到页面的 Header 部分。我们有 3 个部分供此服务使用

  1. 拦截器:将会通知指令。
  2. 通知服务:包含 API 请求的当前繁忙状态。如果有任何正在进行的 API 请求,busy则传递给的值为 true,否则为false。
  3. 指令:将作为 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:

  1. 按照基本步骤来执行HttpInterceptor。
  2. this.beginRequest();在拦截函数的开头调用。它将增加requestCounter 或设置requestCounter为 1(如果小于或等于 0)。如果requestCounter等于 1,它将传递 true 给abns.busyBehaviourSubject。这将通知 ajax-busy-indicator 指令已发起 ajax 请求。
  3. 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:

  1. 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 a show is already pending, don't start a new one.
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 a show is already pending, don't start a new one.
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:

  1. 添加@Directive装饰器AjaxBusyIndicatorDirective类以使其具有指令性。
  2. 将选项添加selector:’[ajax-busy-indicator]’到装饰器。这将使其成为属性指令。现在我们可以添加ajax-busy-indicator到任何元素以应用此指令。
  3. 声明变量showDelay并hideDelay添加@Input 装饰器使其成为指令的输入属性。
  4. 声明showTimer和hideTimer订阅对象,以便我们可以取消订阅显示和隐藏计时器。
  5. cancelPendingHide()将取消订阅 hideTimer 并将cancelPendingShow()取消订阅 showTimer。
  6. 在 ngOnInit 中,我们将为abns.busyBehaviourSubject 添加 2 个订阅者。一个用于隐藏内容(加载动画),另一个用于显示指令内的内容。
  7. 如果传递给的值为abns.busytrue
    a) 它将调用cancelPendingHide()取消订阅hideTimer。
    b) 它将以 showDelay 作为计时器的周期来启动计时器。 在计时器内,它将从’inactive’指令元素中删除类,然后取消订阅计时器并启动计时器null。
  8. 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 });
}
}
));

}

}

  1. 按照基本步骤来执行HttpInterceptor。
  2. 从ngx-toastr导入ToastrService。ToastrService 使用构造函数注入依赖项。
    constructor(private toasterService: ToastrService){}在使用 ToasteService 之前,我们需要使用 npm 安装它:
    npm install ngx-toastr — save
  3. @angular/animations包是默认 toast 所需的依赖项npm install @angular/animations --save
  4. 在拦截函数中,使用管道函数将运算tap符应用于next.handle(request):Observable。点击错误并使用toasterService显示它。

4. 参考文章

Angular HTTP Interceptors : Multiple Interceptors and 6 Code Examples of Interceptors

Angular HTTP 拦截器:配置多个HTTP拦截器和 4 个拦截器代码示例

https://pengtech.net/angular/angular_multiple_interceptors.html

作者

鹏叔

发布于

2024-07-05

更新于

2024-07-11

许可协议

评论