发布于 2026-01-06 4 阅读
0

Angular 的 17 个拦截器完整教程 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

Angular 的 17 个拦截器完整教程

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

Angular 的HttpClient提供了一个名为拦截器的强大功能,它可以作为 HTTP 请求和响应的中间件。在本指南中,我们将探讨关于拦截器的所有内容,从基本概念到高级技巧。

了解拦截器

HttpClient中的拦截器是能够拦截传出的 HTTP 请求和传入的响应的函数或类。拦截器充当 HTTP 请求和响应的中间件。

拦截器就像是Angular中处理HTTP请求和响应的助手。

拦截器的常见用途

  • 添加身份验证标头
  • 重试失败的请求
  • 缓存响应
  • 测量服务器响应时间并记录它们
  • 网络操作进行中时,驱动 UI 元素,例如加载指示器。

拦截器的类型

HttpClient 支持两种主要类型的拦截器:

· 功能性拦截器

· 基于直接信息 (DI) 的拦截器

这两种拦截器都能访问外发请求,并可以在请求发送前对其进行修改。它们还可以在响应到达应用程序代码之前将其拦截。

1. 功能性拦截器:

这些函数接收传出的请求以及代表拦截器链下一步的函数。它们因其行为可预测而备受青睐。

定义功能拦截器
此拦截器会在将请求转发到链中的下一步之前记录传出的请求 URL,并处理错误。

export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      console.error('Logging Interceptor Functional Error:', error);
      return throwError(()=> error);
    })
  );
}
Enter fullscreen mode Exit fullscreen mode

配置功能拦截器

功能拦截器是在 HttpClient 设置过程中使用withInterceptors功能进行配置的:

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptors([loggingInterceptor, cachingInterceptor]),
  )
]});
Enter fullscreen mode Exit fullscreen mode

在这里,我们配置了loggingInterceptorcachingInterceptor。它们将形成一个链,其中loggingInterceptor先处理请求,然后cachingInterceptor再处理请求

拦截响应事件:

拦截器可以转换 next 返回的HttpEvents,从而允许访问或操作响应。

export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req).pipe(tap(event => {
    if (event.type === HttpEventType.Response) {
      console.log(req.url, 'returned a response with status', event.status);
    }
  }));
}
Enter fullscreen mode Exit fullscreen mode

2. 基于直接信息 (DI) 的拦截器:

这些是实现了HttpInterceptor接口的可注入类。它们提供的功能与函数式拦截器类似,但通过 Angular 的依赖注入系统进行配置的方式不同。

定义基于 DI 的拦截器
基于 DI 的拦截器被定义为实现HttpInterceptor接口的可注入类。

该拦截器会在将请求转发到链中的下一步之前,记录传出的请求 URL。

@Injectable()
export class LoggingInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        console.error('Logging Interceptor DI Error:', error);
        return throwError(()=> error);
      })
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

配置基于依赖注入的拦截器
基于依赖注入的拦截器是通过 Angular 的依赖注入系统进行配置的:

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptorsFromDi(),
  ),
  {provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true},
]});
Enter fullscreen mode Exit fullscreen mode

例子

现在让我们创建一个简单的 Angular 项目,并在其中实现函数式拦截器和基于依赖注入的拦截器。

这个简化的示例涵盖了所有提到的功能:添加身份验证标头、重试失败的请求、测量服务器响应时间、在网络操作进行时加载旋转指示器以及记录日志

运行以下命令生成一个新项目:

ng new interceptors-demo
Enter fullscreen mode Exit fullscreen mode

运行以下命令生成新服务:

ng generate service auth
Enter fullscreen mode Exit fullscreen mode

现在,让我们修改src/app目录下的auth.service.ts文件,以管理身份验证并获取身份验证令牌。

// auth.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class AuthService {
  getAuthToken(): string {
    // Logic to retrieve authentication token
    return 'your_auth_token_here';
  }
}
Enter fullscreen mode Exit fullscreen mode

运行以下命令生成新服务:

ng generate service loading
Enter fullscreen mode Exit fullscreen mode

现在,让我们修改src/app目录中的loading.service.ts文件,以显示和隐藏加载指示器 UI 元素。

import { Injectable } from '@angular/core';

@Injectable()
export class LoadingService {
  private loading = false;

  showLoadingSpinner() {
    this.loading = true;
    console.log('Loading spinner shown'); // Log when loading spinner is shown
    // Logic to show loading spinner UI element
  }

  hideLoadingSpinner() {
    this.loading = false;
    console.log('Loading spinner hidden'); // Log when loading spinner is hidden
    // Logic to hide loading spinner UI element
  }

  isLoading(): boolean {
    return this.loading;
  }
}
Enter fullscreen mode Exit fullscreen mode

运行以下命令生成新的拦截器:

ng generate interceptor functional
Enter fullscreen mode Exit fullscreen mode

现在,让我们修改src/app目录中的functional.interceptor.ts文件来实现函数式拦截器:

import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { throwError } from 'rxjs';
import { catchError, finalize, retry } from 'rxjs/operators';
import { LoadingService } from './loading.service';

// Server Response Time Interceptor
export const responseTimeInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const startTime = Date.now();
  return next(req).pipe(
    finalize(() => {
      const endTime = Date.now();
      const responseTime = endTime - startTime;
      console.log(`Request to ${req.url} took ${responseTime}ms`);      
    })
  );
}

// Loading Spinner Interceptor
export const loadingSpinnerInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const loadingService = new LoadingService(); // Instantiate the loading service
  loadingService.showLoadingSpinner(); // Show loading spinner UI element

  return next(req).pipe(
    finalize(() => {
      loadingService.hideLoadingSpinner(); // Hide loading spinner UI element
    })
  );
};

export const authInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const authToken = 'YOUR_AUTH_TOKEN_HERE';

  // Clone the request and add the authorization header
  const authReq = req.clone({
    setHeaders: {
      Authorization: `Bearer ${authToken}`
    }
  });

  // Pass the cloned request with the updated header to the next handler
  return next(authReq);
};

export const retryInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const maxRetries = 3;

  return next(req).pipe(
    retry(maxRetries),
    catchError((error: HttpErrorResponse) => {
      console.error('Retry Interceptor Functional Error:', error);
      return throwError(()=> error);
    })
  );
};


export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req);
}
Enter fullscreen mode Exit fullscreen mode

运行以下命令生成新的拦截器:

ng generate interceptor dibased
Enter fullscreen mode Exit fullscreen mode

现在,让我们修改src/app目录中的dibased.interceptor.ts文件,以实现基于依赖注入的拦截器:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, finalize, retry, tap } from 'rxjs/operators';
import { LoadingService } from './loading.service';
import { AuthService } from './auth.service';

@Injectable()
export class ResponseTimeInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const startTime = Date.now();
    return next.handle(req).pipe(
      tap(event => {
        if (event instanceof HttpResponse) {
          const endTime = Date.now();
          const responseTime = endTime - startTime;
          console.log(`Request to ${req.url} took ${responseTime}ms`);
        }
      })
    );
  }
}

@Injectable()
export class LoadingSpinnerInterceptorDI implements HttpInterceptor {
  constructor(private loadingService: LoadingService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.loadingService.showLoadingSpinner(); // Show loading spinner UI element here
    return next.handle(req).pipe(
      finalize(() => {
        this.loadingService.hideLoadingSpinner(); // Hide loading spinner UI element
      })
    );
  }
}

@Injectable()
export class RetryInterceptorDI implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const maxRetries = 3; // Customize max retry attempts here
    return next.handle(req).pipe(
      retry(maxRetries),
      catchError((error: HttpErrorResponse) => {
        console.error('Retry Interceptor DI Error:', error);
        return throwError(()=> error);
      })
    );
  }
}

@Injectable()
export class AuthInterceptorDI implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authToken = this.authService.getAuthToken();
    const authReq = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${authToken}`)
    });
    return next.handle(authReq);
  }
}

@Injectable()
export class LoggingInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return next.handle(req);
  }
}
Enter fullscreen mode Exit fullscreen mode

现在,我们将在 app.config.ts 文件中配置这两个拦截器。

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
import { authInterceptorFunctional, loadingSpinnerInterceptorFunctional, loggingInterceptorFunctional, responseTimeInterceptorFunctional, retryInterceptorFunctional } from './functional.interceptor';
import { AuthInterceptorDI, LoadingSpinnerInterceptorDI, LoggingInterceptorDI, ResponseTimeInterceptorDI, RetryInterceptorDI } from './dibased.interceptor';
import { LoadingService } from './loading.service';
import { AuthService } from './auth.service';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([
        responseTimeInterceptorFunctional,
        loadingSpinnerInterceptorFunctional, 
        authInterceptorFunctional, 
        retryInterceptorFunctional, 
        loggingInterceptorFunctional, 
      ]),
      /* THE COMMENTED CONFIGURATIONS ARE FOR DI-based INTERCEPTORS 
          Comment withInterceptors() and uncomment the below code to use DI-based interceptors
      */

      //withInterceptorsFromDi(),
    ),
    //LoadingService,
    //AuthService,
    // { provide: HTTP_INTERCEPTORS, useClass: ResponseTimeInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: LoadingSpinnerInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: RetryInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorDI, multi: true }
  ]
};
Enter fullscreen mode Exit fullscreen mode

现在,让我们修改app.component.ts文件,使用 Angular 的 HttpClient 来获取数据:

import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HttpClient } from '@angular/common/http';


@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent implements OnInit{
  constructor(private http: HttpClient) { }

  ngOnInit() {
    this.getData();
  }

  getData() {
    this.http.get('https://jsonplaceholder.typicode.com/posts').subscribe({
      next: data => { 
        console.log(data);
      },
      error: error => {
        console.error('Error getting post:', error);
      }
    });
  }
}
Finally, let’s update the app.component.html file to remove the default content:

<div>
  <h1>Welcome to Angular Interceptors Demo</h1>
</div>
Enter fullscreen mode Exit fullscreen mode

现在,您可以使用以下命令运行该应用程序:

ng serve
Enter fullscreen mode Exit fullscreen mode

这将启动一个开发服务器,您应该能够在浏览器控制台中看到输出。

结论

拦截器是管理 Angular 应用中 HTTP 流量的强大工具。通过拦截请求和响应,开发者可以实现从身份验证和缓存到日志记录和错误处理等各种功能。理解并有效利用拦截器可以显著提升 Angular 应用的可靠性、可维护性、安全性和性能。

文章来源:https://dev.to/bytebantz/angulars-17-interceptors-complete-tutorial-220k