You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular10升级至15后登录时Interceptor持续触发catchError致页面卡顿

Angular 10 升级至 15 后登录环节异常问题修复

核心问题表现

  1. JsogServiceInterceptor 的 catchError 代码块持续触发,但相同逻辑在 Angular 10 环境下可正常运行
  2. AuthenticationGuard 的 checkConnect() 方法在 subscribe(data => {}) 处逻辑异常,请求反复进入 Interceptor 的 if (req.responseType === 'json') 分支,导致登录页面卡顿,替换为 pipe(map()) 后问题依旧

问题根源分析

1. Interceptor 中的计数逻辑错误

finalize 操作符无论请求成功或失败都会执行,已经处理了 activeLogins 的递减操作,但 catchError 中又重复执行 activeLogins--,导致计数异常,进而触发 networkStatus 状态反复切换,引发额外请求逻辑。

同时,代码中直接调用静态方法 AuthenticationGuard.checkConnection() 是错误的——checkConnection 依赖 Guard 实例的 http、store 等服务,静态调用会导致依赖未初始化,触发新的错误。

2. Guard 中的订阅逻辑问题

  • store.select 的订阅未取消,会导致内存泄漏,且每次调用 checkConnect() 都会新增订阅,重复发起 HTTP 请求
  • 嵌套 subscribe 是 RxJS 反模式,会导致请求流无法有效控制,进而引发重复请求和页面卡顿

修复代码示例

修复后的 JsogServiceInterceptor

import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {JsogService} from 'jsog-typescript';
import {catchError, finalize, map} from 'rxjs/operators';
import {throwError, Observable, BehaviorSubject} from "rxjs";
import {AuthenticationGuard} from './guard.service';

@Injectable({
  providedIn: 'root'
})
export class JsogServiceInterceptor implements HttpInterceptor {
   // 改用BehaviorSubject确保初始状态稳定
   private networkStatus: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
   private activeLogins = 0;

   // 注入AuthenticationGuard实例,替代静态调用
   constructor(private jsog: JsogService, private authGuard: AuthenticationGuard) {}

   intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
     if (req.responseType === 'json') {
        // 仅当无活跃请求时发送loading状态
        if (this.activeLogins === 0) {
          this.networkStatus.next(true);
        }
        this.activeLogins++;
        
        return next.handle(req)
          .pipe(
            finalize(() => {
              this.activeLogins--;
              if (this.activeLogins === 0) {
                this.networkStatus.next(false);
              }
            }),
            map(event => {
              if (event instanceof HttpResponse) {
                const body: any = this.jsog.deserialize(event.body);
                return event.clone({ body });
              }
              return event;
            }),
            catchError(err => {
              if (err instanceof HttpErrorResponse) {
                // 移除重复的计数操作,交给finalize统一处理
                this.authGuard.checkConnect();
                // Angular 13+推荐使用工厂函数返回错误
                return throwError(() => err);
              }
              return throwError(() => err);
            })
          );
     }
     return next.handle(req);
   }
}

修复后的 AuthenticationGuard

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { BehaviorSubject, takeUntil, switchMap, tap, catchError, of } from 'rxjs';
import { Store, createFeatureSelector } from '@ngrx/store';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';

export interface UserInfo {}
export interface AuthState { loggedIn: boolean }
export enum RequestStatusEnum { OK }
export interface RequestStatus { status: RequestStatusEnum }

@Injectable({ providedIn: 'root' })
export class AuthenticationGuard implements CanActivate {
  private authenticationData: BehaviorSubject<UserInfo> = new BehaviorSubject<UserInfo>(undefined);
  private destroy$ = new BehaviorSubject<void>(void 0); // 用于清理订阅

  constructor(
    private store: Store<{ authenticationData: AuthState }>,
    private http: HttpClient,
    private router: Router
  ) {}

  checkConnect(): void {
    this.store.select(createFeatureSelector<AuthState>('authenticationData'))
      .pipe(
        takeUntil(this.destroy$), // 组件销毁时自动取消订阅
        switchMap(data => {
          if (!data.loggedIn) return of(null); // 未登录时跳过请求
          // 用switchMap替换嵌套subscribe,控制请求流
          return this.http.get<RequestStatus>(environment.systemServiceURL, { 
            withCredentials: true, 
            reportProgress: false 
          }).pipe(
            tap(res => {
              if (res.status !== RequestStatusEnum.OK) {
                this.router.navigate(['/no-access']);
              }
            }),
            catchError(() => {
              this.store.dispatch({
                type: 'AUTH_SET_LOGGED_OUT', // 补充明确的Action类型
                payload: { loggedIn: false }
              });
              return of(null);
            })
          );
        })
      )
      .subscribe();
  }

  // 清理订阅,避免内存泄漏
  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  canActivate(): boolean | Observable<boolean> {
    // 补充canActivate核心逻辑(根据实际业务调整)
    return this.store.select(createFeatureSelector<AuthState>('authenticationData'))
      .pipe(tap(data => {
        if (!data.loggedIn) this.router.navigate(['/login']);
      }));
  }
}

额外注意事项

  • Angular 13+ 中 throwError 推荐使用工厂函数形式,避免直接传递错误对象导致的潜在问题
  • NGRX 的 Action 必须包含明确的 type 属性,否则状态更新会异常
  • 若仍存在重复请求,可在 HTTP 请求管道中添加 take(1) 操作符,确保只取一次最新值

内容的提问来源于stack exchange,提问作者davidArchA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 18:05:03