如何避免Firebase Auth未完成认证时Login组件闪烁渲染
问题:已认证用户登录后Login组件闪烁
即使用户已完成认证,Login组件仍会出现闪烁现象,核心原因是Firebase Auth的user$可观察对象初始状态导致的:
- 应用首次加载时,Firebase Authentication尚未确定用户是否处于登录状态,此时
user$可观察对象为null,路由守卫会直接重定向至登录页面。 - 尝试通过Loading组件在用户状态未确定时显示加载态,但由于
authGuard.ts中的跳转代码,导致Login组件与加载组件同时闪烁。
解决方案:调整认证守卫与加载状态逻辑
1. 更新AuthService,区分「未确定状态」与「未登录状态」
当前user$的初始值为null,无法区分是「还在加载认证状态」还是「确实未登录」。我们需要改造user$,让它在加载完成前发出特定占位值(比如undefined),同时优化loading状态的更新逻辑:
// auth.ts import { Injectable, inject, signal } from '@angular/core'; import { Auth, createUserWithEmailAndPassword, signInWithEmailAndPassword, user, signOut, UserCredential } from '@angular/fire/auth'; import { Observable, from, BehaviorSubject, switchMap, startWith } from 'rxjs'; import { IUser } from '../interfaces/user'; import { Router } from '@angular/router'; import { UserService } from './user.service'; @Injectable({ providedIn: 'root', }) export class AuthService { router = inject(Router); // 初始发出undefined,表示认证状态未确定 user$: Observable<IUser | null | undefined>; currentUserSig = signal<IUser | null | undefined>(undefined); private loadingSubject = new BehaviorSubject<boolean>(true); loading$ = this.loadingSubject.asObservable(); userService = inject(UserService); constructor(private firebaseAuth: Auth) { // 使用startWith(undefined)标记初始未确定状态 this.user$ = user(this.firebaseAuth).pipe( startWith(undefined), switchMap(fbUser => { if (fbUser) { return new Observable<IUser>(observer => { observer.next({ email: fbUser.email, uid: fbUser.uid }); observer.complete(); }); } else { return new Observable<null>(observer => { observer.next(null); observer.complete(); }); } }) ); this.user$.subscribe((user) => { console.log(user); if (user === undefined) { // 状态未确定,保持加载 this.loadingSubject.next(true); this.currentUserSig.set(undefined); } else { this.loadingSubject.next(false); this.currentUserSig.set(user); } }); } register(email: string, password: string, nome: string, apelido: string): Observable<void> { return from(createUserWithEmailAndPassword(this.firebaseAuth, email, password)).pipe( switchMap((ref: UserCredential) => { const userData = { email: email, nome: nome, apelido: apelido, uid: ref.user.uid, }; return this.userService.addUser(userData); }) ); } login(email: string, password: string): Observable<void> { const promise = signInWithEmailAndPassword( this.firebaseAuth, email, password ).then(() => {}); return from(promise); } logout(): Observable<void> { const promise = signOut(this.firebaseAuth).then(() => { this.router.navigate(['/login']); }); return from(promise); } }
2. 修改AuthGuard,等待认证状态确定后再处理路由
在守卫中,只有当user$发出非undefined的值时,才进行路由判断,避免在加载状态下跳转登录:
// authGuard.ts import { CanActivateFn } from '@angular/router'; import { AuthService } from './auth'; import { inject } from '@angular/core'; import { Router } from '@angular/router'; import { map, filter } from 'rxjs/operators'; export const AuthGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router); return authService.user$.pipe( // 过滤掉未确定状态(undefined),只处理已确定的状态(user或null) filter(user => user !== undefined), map(user => { if (user) { return true; } else { router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); return false; } }) ); };
3. 优化AppComponent的加载逻辑(可选)
直接使用AuthService的loading$进行绑定,避免手动订阅的冗余代码:
// app.component.ts import { Component,inject } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { AuthService } from './services/auth'; import { LoadingComponent } from './layouts/loading/loading.component'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-root', standalone: true, imports: [RouterOutlet,LoadingComponent,CommonModule], templateUrl: './app.component.html', styleUrl: './app.component.css' }) export class AppComponent { authService = inject(AuthService); // 直接复用AuthService的loading状态 loading$ = this.authService.loading$; }
对应的模板简化为:
<!-- app.component.html --> @if(loading$ | async){ <app-loading></app-loading> } @else{ <router-outlet></router-outlet> }
核心原理说明
- 通过
startWith(undefined)给user$添加初始未确定状态,让守卫能区分「正在加载」和「未登录」两种场景 - 守卫中使用
filter(user => user !== undefined)等待认证状态确定后再处理路由,避免在加载时错误跳转登录页面 - 统一管理loading状态,确保AppComponent的加载态与认证状态完全同步,从根源上解决组件闪烁问题
内容的提问来源于stack exchange,提问作者Francisco Soares
相关产品推荐
相关产品推荐

