Angular路由重定向时如何避免加载指示器闪烁
解决Angular路由重定向导致加载指示器闪烁问题
核心问题分析
路由守卫重定向时,会先触发NavigationCancel(原导航取消),紧接着触发新的NavigationStart,导致LoadingService反复执行setLoading(false)和setLoading(true),叠加原有延迟逻辑后,最终出现加载指示器闪烁的现象。
解决方案
1. 给LoadingService添加请求计数器,合并连续加载状态
通过计数器跟踪当前活跃的加载请求,仅在计数器从0变为1时开启加载,从1变为0时关闭加载,避免频繁切换状态。
修改LoadingService:
export class LoadingService { private readonly loadingSubject = new BehaviorSubject<boolean>(false); public readonly loading$ = this.loadingSubject.asObservable(); private timeoutId: ReturnType<typeof setTimeout> | null = null; private loadingCount = 0; // 新增请求计数器 setLoading(isLoading: boolean, delay = 200): void { if (isLoading) { this.loadingCount++; // 仅第一个加载请求触发延迟显示逻辑 if (this.loadingCount === 1) { this.timeoutId = setTimeout(() => { this.loadingSubject.next(true); }, delay); } } else { this.loadingCount = Math.max(0, this.loadingCount - 1); // 所有加载请求完成后才关闭指示器 if (this.loadingCount === 0) { if (this.timeoutId !== null) { clearTimeout(this.timeoutId); this.timeoutId = null; } this.loadingSubject.next(false); } } } }
2. 优化路由事件监听,忽略重定向的中间状态
通过导航ID跟踪连续的重定向操作,合并NavigationCancel和后续的NavigationStart,避免不必要的状态切换。
修改AppComponent中的路由事件处理:
private currentNavigationId?: number; constructor( private router: Router, private _loadingService: LoadingService, private _destroyRef: DestroyRef ) { this.router.events .pipe(takeUntilDestroyed(this._destroyRef)) .subscribe((event) => { if (event instanceof NavigationStart) { // 仅当不是连续重定向的导航时,才触发加载开启 if (!this.currentNavigationId || event.id !== this.currentNavigationId) { this._loadingService.setLoading(true); this.currentNavigationId = event.id; } } else if (event instanceof NavigationCancel) { // 记录取消的导航ID,用于判断后续是否为重定向 this.currentNavigationId = event.id; // 不立即关闭加载,等待新导航启动 } else if ( event instanceof NavigationEnd || event instanceof NavigationError ) { this.currentNavigationId = undefined; this._loadingService.setLoading(false); } }); }
3. 移除登录逻辑中的手动loading调用
登录成功后的setLoading(true, 0)会与路由事件触发的loading状态重复,导致计数器异常叠加,直接移除该调用即可,路由事件会自动处理加载状态:
this.authService .login(email, password) .pipe(take(1)) .subscribe({ next: () => { const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/devices'; // 移除手动setLoading调用 this.router.navigate([returnUrl]); }, // 错误场景手动关闭加载 error: () => { this.loadingService.setLoading(false); } });
4. 可选:添加最小显示时间(彻底避免快速闪烁)
如果希望加载指示器至少显示固定时长,防止快速闪显,可以给LoadingService添加最小显示时间逻辑:
export class LoadingService { private readonly loadingSubject = new BehaviorSubject<boolean>(false); public readonly loading$ = this.loadingSubject.asObservable(); private timeoutId: ReturnType<typeof setTimeout> | null = null; private loadingCount = 0; private showStartTime?: number; private readonly minShowDuration = 300; // 最小显示300ms setLoading(isLoading: boolean, delay = 200): void { if (isLoading) { this.loadingCount++; if (this.loadingCount === 1) { this.timeoutId = setTimeout(() => { this.showStartTime = Date.now(); this.loadingSubject.next(true); }, delay); } } else { this.loadingCount = Math.max(0, this.loadingCount - 1); if (this.loadingCount === 0) { if (this.timeoutId !== null) { clearTimeout(this.timeoutId); this.timeoutId = null; } // 计算已显示时长,不足最小时长则延迟关闭 if (this.showStartTime) { const elapsed = Date.now() - this.showStartTime; if (elapsed < this.minShowDuration) { setTimeout(() => { this.loadingSubject.next(false); this.showStartTime = undefined; }, this.minShowDuration - elapsed); return; } } this.loadingSubject.next(false); this.showStartTime = undefined; } } } }
内容的提问来源于stack exchange,提问作者user p
相关产品推荐
相关产品推荐

