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

Angular路由与Token刷新问题:实现父子组件顺序加载数据

解决方案:避免重复Token刷新+父组件优先加载数据

一、拦截器层面解决重复刷新Token问题

当前拦截器会在每个401请求时独立发起刷新Token请求,这是重复请求的核心原因。通过共享刷新请求Observable可以彻底解决这个问题:

1. 在AuthService中维护刷新状态与共享请求

@Injectable({ providedIn: 'root' })
export class AuthService {
  private refreshTokenInProgress$: Observable<TokenApiModel> | null = null;

  // 原有getToken、setToken等方法保留

  getRefreshTokenRequest(apiToken: TokenApiModel): Observable<TokenApiModel> {
    if (!this.refreshTokenInProgress$) {
      // 无正在进行的刷新请求时,发起新请求并缓存Observable
      this.refreshTokenInProgress$ = this.accountService.renewToken(apiToken).pipe(
        finalize(() => {
          // 请求完成后重置状态,允许后续新的刷新请求
          this.refreshTokenInProgress$ = null;
        })
      );
    }
    return this.refreshTokenInProgress$;
  }
}

2. 修改TokenInterceptor的错误处理逻辑

handleUnAuthorizedError(request: HttpRequest<any>, next: HttpHandler) {
  const apiToken: TokenApiModel = {
    token: this.authService.getToken(),
    refreshToken: this.authService.getRefreshToken()
  };

  return this.authService.getRefreshTokenRequest(apiToken).pipe(
    switchMap((data) => {
      this.authService.setToken(data.token);
      this.authService.setRefreshToken(data.refreshToken);
      this.authService.setId(data.id);
      const clonedRequest = request.clone({
        setHeaders: {
          Authorization: `Bearer ${data.token}`
        }
      });
      return next.handle(clonedRequest);
    }),
    catchError((err) => {
      // 仅在刷新失败时执行登出与跳转
      this.authService.logout();
      this.messageService.add({
        severity:'warn',
        summary: 'Warn',
        detail: 'Token Expired, Please Login Again',
        life:3000
      });
      this.route.navigate(['/admin/login']);
      return throwError(() => err);
    })
  );
}

所有同时触发的401请求会共享同一个刷新Token的Observable,只会向后端发送一次刷新请求。

二、实现父组件先加载数据,再加载子组件

方案1:使用路由Resolve守卫

通过Resolve守卫在父路由激活前预加载数据,数据加载完成后才会渲染父组件与子组件:

1. 创建Resolve守卫

@Injectable({ providedIn: 'root' })
export class ParentDataResolver implements Resolve<any> {
  constructor(private parentService: ParentService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
    // 调用父组件所需的数据加载API
    return this.parentService.fetchParentData();
  }
}

2. 更新路由配置

{
  path: 'parent',
  component: ParentComponent,
  resolve: {
    parentData: ParentDataResolver // 绑定守卫,预加载数据
  },
  children: [
    { path: 'child1', component: Child1Component },
    { path: 'child2', component: Child2Component }
  ]
}

3. 父组件中获取预加载的数据

export class ParentComponent implements OnInit {
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    const parentData = this.route.snapshot.data['parentData'];
    // 处理预加载的数据,如存入服务或组件属性
  }
}

方案2:父组件控制子路由渲染

在父组件模板中,仅当数据加载完成后才渲染子路由的出口:

1. 父组件逻辑

export class ParentComponent implements OnInit {
  isDataLoaded = false;

  constructor(private parentService: ParentService) {}

  ngOnInit() {
    this.parentService.fetchParentData().subscribe({
      next: (data) => {
        // 处理数据
        this.isDataLoaded = true; // 标记数据加载完成
      },
      error: (err) => {
        // 处理加载错误
      }
    });
  }
}

2. 父组件模板

<!-- 父组件自身内容 -->
<div *ngIf="isDataLoaded">
  <!-- 数据加载完成后才渲染子路由 -->
  <router-outlet></router-outlet>
</div>

子组件会在父组件数据加载完成后才被创建,其API请求自然延后发起。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:05:40