Angular+Firebase项目刷新页面后HandleAuthService滞后无法获取uid如何解决?
问题成因
- AngularFire的
authState是异步流,页面刷新时Firebase需要先和后端校验用户会话有效性,这个异步请求的返回时机晚于组件、服务的初始化生命周期,你当前直接同步读取currentUserState属性时,authState还没返回结果,属性未赋值自然返回undefined。 - 本质问题是把异步状态当成同步值使用,没有遵循响应式数据流的设计逻辑,并非
HandleAuthService执行优先级低。
解决方案
第一步:改造HandleAuthService
用BehaviorSubject缓存用户状态,暴露可观察的用户流,替代直接读取同步属性:
import { Observable, BehaviorSubject } from 'rxjs'; import { map, take, filter } from 'rxjs/operators'; export class HandleAuthService { // 缓存用户状态,初始值为null private currentUserSubject = new BehaviorSubject<any>(null); // 对外暴露只读的可观察用户流 public currentUser$ = this.currentUserSubject.asObservable(); constructor(private fireAuth: AngularFireAuth, private ngZone: NgZone, private router: Router) { this.fireAuth.authState.subscribe(user => { if (user) { const userState = { uid: user.uid!, email: user.email!, displayName: user.displayName! }; this.currentUserSubject.next(userState); localStorage.setItem('user', JSON.stringify(userState)); } else { this.currentUserSubject.next(null); localStorage.setItem('user', "null"); } }) } // 可选:同步获取当前用户的快捷方法,注意未登录时返回null get currentUserSync() { return this.currentUserSubject.value; } login() { return this.googleLogin( new firebase.auth.GoogleAuthProvider()); } googleLogin(provider:any) { return this.fireAuth.signInWithPopup(provider) .then((result)=> { this.ngZone.run(() => { this.router.navigate(['data']); }); this.setUser(result.user); }) .catch((error) => { console.log(error); }) } setUser(result:any) { const userState = { uid : result.uid, email : result.email, displayName : result.displayName }; this.currentUserSubject.next(userState); } }
第二步:改造data.component.ts
订阅用户状态流,等用户信息返回后再执行业务逻辑:
import { Subscription } from 'rxjs'; export class DataComponent implements OnInit, OnDestroy { private userSub?: Subscription; currentUserId?: string; constructor(private authService: HandleAuthService, private crowboxService: CrowboxdbService) {} ngOnInit(): void { this.userSub = this.authService.currentUser$.subscribe(user => { if (user) { this.currentUserId = user.uid; console.log("Current User Id is ", this.currentUserId); this.checkIfUserExists(); // 同步uid给CrowboxdbService this.crowboxService.setCurrentUserId(user.uid); } else { // 未登录跳转回首页 this.router.navigate(['/home']); } }) } ngOnDestroy(): void { // 取消订阅避免内存泄漏 this.userSub?.unsubscribe(); } }
第三步:改造CrowboxdbService
依赖用户流获取uid,避免在构造函数中同步读值:
import { switchMap } from 'rxjs/operators'; import { Observable } from 'rxjs'; export class CrowboxdbService { private currentUserId?: string; constructor(private db: AngularFireDatabase, private handleAuth: HandleAuthService) {} // 示例:封装查询用户数据的方法,自动绑定用户uid getUserData(): Observable<any> { return this.handleAuth.currentUser$.pipe( filter(user => !!user), switchMap(user => this.db.object(`/users/${user.uid}`).valueChanges()) ) } // 手动设置uid的适配方法 setCurrentUserId(uid: string) { this.currentUserId = uid; } }
进阶优化(可选):添加路由守卫
在路由层面校验登录状态,保证进入data页时用户状态已确定:
@Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private auth: HandleAuthService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { return this.auth.currentUser$.pipe( map(user => { if (user) return true; this.router.navigate(['/home']); return false; }), take(1) ) } }
在路由配置中给data路由添加canActivate: [AuthGuard]即可生效。
内容的提问来源于stack exchange,提问作者Hamza Qureshi
相关产品推荐
相关产品推荐

