DevUI is a team with both design and engineering perspectives, serving huawei DevCloud platform and huawei internal background systems, as well as designers and front-end engineers. Ng Component Library: Ng-Devui (Welcome Star)

The introduction

Angular HTTP interceptor Intercept requests and responses, in fact description is very clear, in our team the actual business development, the main practice is through to encapsulate interceptors, to implement the handling of HTTP requests and responses, surveillance, the two links can do processing scene has a lot of, such as the request header field to modify, security authentication request, Interface data caching, request interception, monitoring processing, etc., this paper mainly shares three practices: 1, the setting of the request header, 2, interface data caching, 3, response processing. The implementation of the code is actually very simple, nonsense not to say, directly look at the code. Angular versions 7.0, 6 and above should be supported.

Read the Official Angular Chinese documentation interceptor section: angular.cn/guide/http#… .

start

We need to implement the HttpInterceptor interface, and then implement the intercept() method. The code looks like this:

import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; import { Observable } from 'rxjs'; export class ApiInjector implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req); }}Copy the code

Intercept asks us to return an Observable that the component can subscribe to. This method takes two parameters:

Req: HttpRequest type, this is our HTTP request object;

HttpHandler is an abstract class that provides a handle() method that converts HTTP requests into An Observable of the HttpEvent type. HttpHandler handles the response from the server. So inside this method, we’ve got request and response, and we can basically do whatever we want.

Once declared, we need to inject the interceptor into the root module of our app

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,
    BrowserAnimationsModule,
  ],
  declarations: [
    AppComponent,
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: ApiInjector, multi: true }
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }
Copy the code

Directly using this way, we can use this interceptor, but direct injection, with statement of the interceptor more and more with the reduce of maintainability and suggest that we add an index. The ts file all the interceptors encapsulated into an array object and then exposed, can open a new folder inside the project, I then put the custom interceptor and integrated TS files in a folder. The code for index.ts is simple as follows

import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { ApiInjector } from './apiinjector'; Export const HttpInterceptorProviders = [{provide: HTTP_INTERCEPTORS, useClass: ApiInjector, multi: true}]Copy the code

Module. ts providers: / / Add interceptorProviders to app.module.ts providers: / / Add interceptorProviders to app.module.ts providers

So the question is, what happens to the sequence of interceptors?

Its execution order is actually the index subscript order of the array of export 0->1->2, which is written here. When we re-call the interface in the project, we can print the two parameters of the Intercept method in the interceptor.

Scenario 1: Intercept a request and modify the request parameters

A common business scenario is to add user authentication parameters such as CFTK and cookie in the request header. Take cookie as an example.

HttpRequest does not have a set** method, and headers is a read-only attribute. The clone() method supports passing in custom headers. Here we modify the parameterizing function our whitespace interceptor uses to handle requests;

Modify the intercept method of the empty ApiInjector class we wrote earlier. The code looks like this:

intercept(req: HttpRequest<any>, next: HttpHandler) {
  const resetReq = req.clone({ setHeaders: {'Appraisal_Cookie', 'Amazing_039'}});
  console.log(resetReq, 'resetReq');
  return next.handle(resetReq);
}
Copy the code

Then check our HTTP request in Chrome F12:

Scenario 2: Capture the response and cache the data

In single-page applications, there is basically an unavoidable point, which is the global sharing of part of the data. Before our team, the most common way to achieve global sharing of data is to use RXJS to broadcast in one place and subscribe everywhere.

Injects a global singleton service into app.module.ts.

public cacheDate = new BehaviorSubject<any>(null);
Copy the code

// After we get the data,

this.cacheDate.next('sharedData');
Copy the code

// Then the subcomponent below gets the global cached value by subscription

cacheDate.asObservable().subscribe(res => {console.log(res)});
Copy the code

Now let’s take a look at caching interface data with the help of an interceptor. We’ve added a new CacheInjector to use as an interceptor for data caching. Using the following implementation, we can maintain the interface data we need to cache by maintaining the cachebleUrlList array object.

import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http'; import { filter, tap } from 'rxjs/operators'; import { of } from 'rxjs'; export class CacheInjector implements HttpInterceptor { private cachebleUrlList = ['/deliverboard/v1/userBoard/userInfo']; private cacheRequetMap = new Map(); Intercept (req: HttpRequest<any>, next: HttpHandler) {// Determine whether to cache this request if (! this.canCacherReq(req)) { return next.handle(req); } const cachedResponse = this.cacherequetmap.get (req.url); if (cachedResponse) { return of(cachedResponse); } // If there is no initialization, Return next.handle(req).pipe(filter(event => event instanceof HttpResponse), Tap (event => {console.log(event, 'response event '); this.cacheRequetMap.set(req.url, event); }})) / / whether the current request need to cache canCacherReq (the req: HttpRequest < any >) : Boolean {return this. CachebleUrlList. IndexOf (the req. Url)! = = 1; }}Copy the code

Then add a reference to our new interceptor to our index.ts file, and our cache interceptor will take effect.

export const HttpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: ApiInjector, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: CacheInjector, multi: true },
];
Copy the code

The above two methods can achieve data caching, you can take according to the actual business scenarios, in addition to consider the concurrency of the interface and large data volume problems.

Based on the CacheInjector code, we have derived a slightly more refined interceptor that exposes two additional static methods for the business layer to query the list of urls that have been cached and to clear the cache interface. This allows us to address the fact that some of our data caches have changed since we developed them. At this point we need to retrieve a new HTTP request; The code looks like this:

import { HttpInterceptor, HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http'; import { filter, tap } from 'rxjs/operators'; import { of } from 'rxjs'; export class CacheInjector implements HttpInterceptor { static cachebleUrlList = ['/deliverboard/v1/userBoard/userInfo']; static cacheRequetMap = new Map(); Intercept (req: HttpRequest<any>, next: HttpHandler) {// Determine whether to cache this request if (! this.canCacherReq(req)) { return next.handle(req); } / / to determine whether a target value of the cache request initialization const cachedResponse = CacheInjector. CacheRequetMap. Get (the req. Url); if (cachedResponse) { return of(cachedResponse); } // If there is no initialization, Return next.handle(req).pipe(filter(event => event instanceof HttpResponse), Tap (event => {console.log(event, 'response event '); CacheInjector.cacheRequetMap.set(req.url, event); })); } / / whether the current request need to cache canCacherReq (the req: HttpRequest < any >) : Boolean {return CacheInjector. CachebleUrlList. IndexOf (the req. Url)! = = 1; } / / query cache interface list static getCachedUrlList () : string [] {return [... CacheInjector. CacheRequetMap. Keys ()]; } / / external active refresh the static refreshReq (the req) {CacheInjector. CacheRequetMap. Delete (the req); }}Copy the code

Scenario 3: Catch response errors and focus on processing

Some special operations in the web project, the user’s scenario, or development brother prepare for bugs little surprise, can lead to our request as an error, we can write a capture error at this time of the interceptor, facing the request of error in the interceptor for unified handling, please donate said, under the unified existing interceptors folder directory, Add a new file CatchErrorInjector (CatchErrorInjector)

import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http'; import { catchError, retry } from 'rxjs/operators'; export class CatchErrorInjector implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler) {HttpHandler) {return next.handle(req). Pipe (catchError(err => {return next.handle(req). Pipe (catchError(err => { Console. log(err, 'back-end interface error '); throw err; })); }}Copy the code

After this processing, it is also easy for us to internationalize the error fields. But, but, sometimes our request will be inexplicable error, especially in the network is unstable, as long as you adjust the posture again, you will get a normal result.

In this case, we can use Retry to update the Intercept method as follows and add retry mechanism.

intercept(req: HttpRequest<any>, next: HttpHandler) {HttpHandler) {return next.handle(req). Pipe (catchError(err => {return next.handle(req). Pipe (catchError(err => { Console. log(err, 'back-end interface error '); throw err; })); retry(1) }Copy the code

Retry The retry is performed immediately after an interface error occurs. The retryWhen operator is the main parameter of the catchError catchError. The retryWhen operator is the main parameter of the catchError catchError; the retryWhen operator is the main parameter of the catchError catchError; In the case of an error to inform the user of our message, OK, the final code is this style:

import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http'; import { catchError } from 'rxjs/operators'; const MAX_RETRY_NUM = 2; export class CatchErrorInjector implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler) { let count = 0; return next.handle(req).pipe( catchError((err, err$) => { count++; // Err $represents an upstream Observable. When returned directly, the catchError retry mechanism is enabled. const tip = err.status === 200 ? Err.body.error. Reason: 'System busy, please try again later '; Console. log(tip, 'back-end interface error '); If (err.status === 400 && count < MAX_RETRY_NUM) {console.log(count, 'retry times '); return err$; } else { throw err; }})); }}Copy the code

conclusion

Ok, so there are three small explorations of httpClient interceptors in business development:

1. Request header parameters are updated;

2. Interface data cache;

3. Error set handling of HTTP responses.

This is mainly a piece of advice. You can also take a look at the introduction of interceptors in the Chinese official document I posted above, which is similar with minor differences.

A demand point on the line generally have a variety of ways to achieve, the so-called road thousands of, the speed of the first, more weapons in the hand, the code shuttle ha naturally fast.

Finally, business development is unpretentious and boring, so it would be nice to take a look at the documentation, update the implementation method, and refactor the business components to give a quiet delivery a jolt.

Join us

We are DevUI team, welcome to come here and build elegant and efficient human-computer design/research and development system with us. Email: [email protected].

The text/DevUI ixiaoxiaomi

Previous articles are recommended

Angular Best Practices

Practice of Micro Front End in Enterprise Application (PART 1)

“How to build a grayscale Publishing environment”