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
相关产品推荐
相关产品推荐

