You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

组件初始化时如何正确等待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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 03:45:03