Как сделать угловой модуль, чтобы игнорировать перехватчик http, добавленный в основной модуль

У меня есть основной модуль с HttpInterceptor для обработки авторизации, и я включаю этот модуль в AppModule, таким образом, все другие модули, использующие HttpClient, используют этот перехватчик.

@NgModule({
  imports: [],
  declarations: [],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true,
    },
  ]
})
export class CoreModule { }

Как заставить модуль обходить перехватчик по умолчанию?

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: components,
  providers: [CustomService],
  exports: components,
})
export class ModuleWithoutInterceptorModule { }

5 ответов

Решение

В соответствии с этим предложением на GitHub мы внедрили простой заголовок для определения запросов, которые не должны быть перехвачены. В перехватчике:

export const InterceptorSkipHeader = 'X-Skip-Interceptor';

@Injectable()
export class SkippableInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.headers.has(InterceptorSkipHeader)) {
      const headers = req.headers.delete(InterceptorSkipHeader);
      return next.handle(req.clone({ headers }));
    }

    ...  // intercept
  }

}

Затем всякий раз, когда вы хотите пропустить перехват для определенного запроса:

const headers = new HttpHeaders().set(InterceptorSkipHeader, '');

this.httpClient
    .get<ResponseType>(someUrl, { headers })
    ...

Обратите внимание, что с помощью этого метода служба, а не перехватчик, выбирает, когда применяется логика перехватчика; это означает, что службы должны "что-то" знать о перехватчиках в вашем приложении. В зависимости от вашего варианта использования, может быть лучше, чтобы перехватчики решали, когда применять логику.

Вы можете использовать HttpBackend.

Пример:

import { HttpClient, ..., HttpBackend } from '@angular/common/http';

@Injectable()
export class TestService {

  private httpClient: HttpClient;

  constructor( handler: HttpBackend) { 
     this.httpClient = new HttpClient(handler);
  }
....

Таким образом, служба не перехватывается AuthInterceptor.

Чтобы обойти все перехватчики, мы можем использовать HttpBackend, как предлагает @deg.

В других случаях мы можем создать фабрику HttpClient, которая позволит нам исключить перехватчики из цепочки перехватчиков:

import { createHttpClient } from './http-client.factory';
...

@Injectable({
  providedIn: 'root'
})
export class TodosApiService {
  http = createHttpClient(this.injector, [Interceptor2]);
                                          ^^^^^^^^^^^^
                                      Interceptors to exclude

  constructor(private injector: Injector) { }

  getTodo() {
    // Interceptor2 will be bypassed
    return this.http.get('https://jsonplaceholder.typicode.com/todos')
  }
}

Пример ng-run

Обратите внимание, что вы можете повторно использовать эту логику, создав базовый класс:

@Injectable()
export class BasicHttpClient {
  protected http = createHttpClient(this.injector, [Interceptor2]);

  constructor(private injector: Injector) { }
}

@Injectable({ providedIn: 'root' })
export class TodosApiService extends BaseHttpClient {

  getTodo() {
    // Interceptor2 will be bypassed
    return this.http.get('https://jsonplaceholder.typicode.com/todos')
  }
}

http-client.factory.ts

import {
  HTTP_INTERCEPTORS,
  HttpBackend,
  HttpClient,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { Injector, Type } from '@angular/core';

class HttpInterceptorHandler implements HttpHandler {
  constructor(private next: HttpHandler, private interceptor: HttpInterceptor) {}

  handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
    return this.interceptor.intercept(req, this.next);
  } 
}

class HttpInterceptingHandler implements HttpHandler {
  private chain: HttpHandler | null = null;

  constructor(
    private backend: HttpBackend,
    private injector: Injector,
    private interceptorsToExclude: Type<HttpInterceptor>[],
    private intercept?: (req: HttpRequest<any>) => HttpRequest<any>
  ) {}

  handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
    if (this.intercept) {
      req = this.intercept(req);
    }

    if (this.chain === null) {
      const interceptors = this.injector
        .get(HTTP_INTERCEPTORS, [])
        .filter(
          interceptor => !this.interceptorsToExclude.some(interceptorType => interceptor instanceof interceptorType)
        );

      this.chain = interceptors.reduceRight(
        (next, interceptor) => new HttpInterceptorHandler(next, interceptor),
        this.backend
      );
    }
    return this.chain.handle(req);
  }
}

export function createHttpClient(
  injector: Injector,
  excludedInterceptors: Type<HttpInterceptor>[],
  intercept?: (req: HttpRequest<any>) => HttpRequest<any>
) {
  return new HttpClient(
    new HttpInterceptingHandler(injector.get(HttpBackend), injector, excludedInterceptors, intercept)
  );
}

Начиная с Angular 12, это можно сделать с помощьюHttpContextнедвижимость дляHttpRequest!

Этот подход удобен тем, что не требует никакой логики для удаления заголовков и может выполняться для каждого запроса отдельно.

      export const DISABLE_GLOBAL_EXCEPTION_HANDLING = new HttpContextToken<boolean>(() => false);

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (this.ignoreErrorHandling(request)) {
            return next.handle(request);
        }

        return next.handle(request).pipe(
            catchError((error) => {
                // normal intercepting logic
                return throwError(() => error);
            }),
        );

    private ignoreErrorHandling(request: HttpRequest<any>) {
        return request.context.get(DISABLE_GLOBAL_EXCEPTION_HANDLING);
    }

Отдельный запрос можно отключить, добавив токен:

      this.httpClient.get<ResponseType>(`/api`, {
    context: new HttpContext().set(DISABLE_GLOBAL_EXCEPTION_HANDLING, true),
});

Если используется в зависимости вне вашего контроля (например, в сторонней библиотеке), вы все равно можете использоватьHttpContextTokensкоторые были добавлены в Angular v12 с помощьюHttpClientзаводской поставщик. Вы можете распространить это на отдельные модули или компоненты, используя массив:

отключить-interceptor.handler.ts

      import { HttpContextToken, HttpHandler, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';

export const DISABLE_INTERCEPTORS = new HttpContextToken<boolean>(() => false);

@Injectable()
export class DisableInterceptorHandler extends HttpHandler {
  constructor(private httpHandler: HttpHandler) {
    super();
  }

  handle(req: HttpRequest<unknown>) {
    return this.httpHandler.handle(
      req.clone({
        context: req.context.set(DISABLE_INTERCEPTORS, true),
      }),
    );
  }
}

Предоставьте обработчик в вашем компоненте/модуле/сервисе/независимоprovidersмножество

      providers: [
  DisableInterceptorHandler,
  {
    provide: HttpClient,
    useFactory: (handler: DisableInterceptorHandler) => new HttpClient(handler),
    deps: [DisableInterceptorHandler],
  },
],

Уважайте HttpContextToken в своем перехватчике!

      import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { DISABLE_INTERCEPTORS } from './disable-interceptor.handler';

@Injectable()
export class AuthenticationInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    if (request.context.get(DISABLE_INTERCEPTORS) === true) {
      return next.handle(request);
    }

    return next.handle(request).pipe(
      // add your typical interceptor logic
    );
  }
}
Другие вопросы по тегам