组件初始化时如何正确等待Firestore文档加载?
问题描述
用户首次登录仪表盘并跳转至私有视图后,通过Firestore获取用户数据时出现异常:进入仪表盘布局时user变量为undefined,依赖该数据的子组件无法正常显示;但刷新页面后,数据能正确赋值给CustomUser接口,所有组件恢复正常。
现有实现代码
Auth服务初始版本(Observable实现)
getUser():Observable<DocumentSnapshot<DocumentData>> { const uid = this.user$?.uid; const userRef = doc(this.firestore,'users/' + uid) return from( getDoc(userRef) ); }
Dashboard布局组件实现
export class DashboardLayoutComponent implements OnInit{ constructor(private auth:AuthService) {} public user:CustomUser; public loading:boolean = true; ngOnInit(){ this.auth.getUser().subscribe(res =>{ this.luxorUser = res.data() as CustomUser; this.loading = false; }); } }
尝试的Promise版本实现
Auth服务代码
async getUser():Promise<CustomUser> { const uid = this.user$?.uid; const userRef = doc(this.firestore, 'users/' + uid ); const snap = await getDoc(userRef); return snap.data() as CustomUser; }
对应组件代码
export class DashboardLayoutComponent implements OnInit{ constructor(private auth:AuthService) {} public $userData:Promise<CustomUser> public loading:boolean = true; ngOnInit(){ this.retrieveUserData(); } retrieveUserData() { this.$userData = this.auth.getUser(); } }
HTML模板
<div *ngIf="$userData | async" > <div class="layout-wrapper" style="background-color: var(--surface-b);"> <app-dashboard-topbar [user]="$userData | async" /> <div class="layout-sidebar"> <app-dashboard-sidebar/> </div> <div class="layout-main-container"> <div class="layout-main"> <router-outlet></router-outlet> </div> </div> <div class="layout-mask"></div> </div> </div>
已尝试的方案
- 使用Promise替代Observable,通过async/await处理异步逻辑
- 在HTML中通过
loading布尔变量实现骨架屏,仅在加载完成后渲染仪表盘,但加载完成后CustomUser仍未赋值 - 在构造函数而非
OnInit生命周期中调用服务
问题根源
核心问题是首次登录跳转时,Auth的用户状态还未完全同步,导致this.user$?.uid为undefined。此时Firestore查询使用无效UID,自然无法获取数据;刷新页面后,用户状态已持久化并同步完成,能正确获取UID,查询也就正常了。
修复方案
1. 改造Auth服务,确保用户状态就绪后再查询
修改服务逻辑,监听用户认证状态流,仅在拿到有效用户实例后发起Firestore查询:
// Auth服务中 getUser(): Observable<CustomUser | null> { return this.user$.pipe( filter(user => !!user), // 过滤未登录/状态未就绪的情况 switchMap(user => { const userRef = doc(this.firestore, `users/${user.uid}`); return from(getDoc(userRef)).pipe( map(snap => snap.data() as CustomUser) ); }) ); }
2. 组件中正确订阅数据
移除手动管理的loading变量,直接订阅服务返回的Observable:
export class DashboardLayoutComponent implements OnInit { public user$: Observable<CustomUser | null>; constructor(private auth: AuthService) {} ngOnInit() { this.user$ = this.auth.getUser(); } }
3. HTML模板优化渲染
利用async管道自动管理订阅,通过as语法复用数据,避免重复订阅:
<!-- 数据就绪时渲染内容 --> <div *ngIf="user$ | async as user"> <div class="layout-wrapper" style="background-color: var(--surface-b);"> <app-dashboard-topbar [user]="user" /> <div class="layout-sidebar"> <app-dashboard-sidebar/> </div> <div class="layout-main-container"> <div class="layout-main"> <router-outlet></router-outlet> </div> </div> <div class="layout-mask"></div> </div> </div> <!-- 加载状态提示(可选) --> <div *ngIf="!(user$ | async)" class="loading-skeleton"> <!-- 骨架屏内容 --> </div>
额外优化建议
- 为Firestore查询添加错误处理,避免因网络问题或数据不存在导致组件崩溃:
// Auth服务中新增错误处理 getUser(): Observable<CustomUser | null> { return this.user$.pipe( filter(user => !!user), switchMap(user => { const userRef = doc(this.firestore, `users/${user.uid}`); return from(getDoc(userRef)).pipe( map(snap => snap.data() as CustomUser), catchError(err => { console.error('获取用户数据失败:', err); return of(null); }) ); }) ); }
内容的提问来源于stack exchange,提问作者Pipo Design
相关产品推荐
相关产品推荐

