在 AngularFire 中检索嵌套 Firestore 查询的 RxJS Observable

Posted

技术标签:

【中文标题】在 AngularFire 中检索嵌套 Firestore 查询的 RxJS Observable【英文标题】:Retrieving an RxJS Observable of a nested Firestore query in AngularFire 【发布时间】:2021-08-23 20:05:07 【问题描述】:

我正在尝试将带有 DocumentReferences 的 Object 的 Observable 转换为我的整个 Object 的 Observable。

我的 Firestore 查询返回 QuestDocument 的 Observable,如下所示(去除原始类型):

export interface QuestDocument 
    ...
    owner: DocumentReference<User>;
    ...
    collaborators?: DocumentReference<User>[];
    categories?: DocumentReference<Category>[];

在我的转换器中,我可以调用其他 Firestore 服务来检索 DocumentReferences 到 UserCategory 的值(扁平结构,所以这里没有问题)。

我的目标是创建一个 Quest 类型的 Observable,但我的嵌套 Observable 没有被正确解析。

export interface Quest 
  ...
  owner: User;
  ...
  collaborators?: User[];
  categories?: Category[];

这是我目前所拥有的:

doc$(docId: string): Observable<Quest> 
  return this.doc(docId).valueChanges()
  .pipe(
    mergeMap(questDoc => 
      const owner$ = this.userService.doc$(questDoc.owner.id);
      const collaborators$ = forkJoin(questDoc.collaborators.map(
        (userRef: DocumentReference) => 
          return this.userService.doc$(userRef.id)
        
      ));
      const categories$ = forkJoin(questDoc.categories.map(
        (categoryRef: DocumentReference) => this.categoryService.doc$(categoryRef.id)
      ));
      const joined = forkJoin(
        owner: owner$,
        collaborators: collaborators$,
        categories: categories$
      );
      joined.subscribe(data => console.log(data));
      return joined.pipe(
        map(value => 
          return Object.defineProperties(questDoc, 
            qid:  value: docId ,
            owner:  value: value.owner ,
            collaborators:  value: value.collaborators ,
            categories:  value: value.categories 
          ) as Quest;
        )
      )
    )
  );

所有类型都匹配,我应该收到一个Observable&lt;Quest&gt;,但是当我尝试打印该值时,它返回undefined,而第二个.pipe() 永远不会到达。

【问题讨论】:

forkJoin 只会在所有源 observables 都发射并完成后发射。对categoryService.doc$()userService.doc$() 的调用会返回完整的可观察对象吗?如果没有,您可以将 .pipe(first()) 添加到您的 joined forkJoin 中的 3 个来源中的每一个。 我不确定 completion 在 Observable 方面的含义,因为我对这些概念还很陌生。两个服务的.doc$() 调用返回各自类文档类型的Observables(参见QuestDocument),映射到实现的类型(仅将文档的ID 添加到CategoryUser)。检索类文档类型的底层函数是 AngularFire 的 Firestore SDK 的扩展。 【参考方案1】:

您没有正确使用forkJoin 运算符。

一个完整的 observable 将不再发出数据。把它当作一个封闭的管道。 ForkJoin 将等待所有流完成(关闭),然后再发出一个数据。

如果您使用 this.afs.collection(...).doc(...).valueChanges() 获取数据,这些 observable 将保持活动状态。每次在 firestore 中更新数据时,它们都会发出。

要完成它们,请使用 take(1)first()(它们将发出一次然后完成),或使用 combineLatest() 组合活动流并实时更新您的数据(不要忘记取消订阅 onDestroy以防止任何内存泄漏)。

这是一个完整流的示例:

doc$(docId: string): Observable<Quest> 
  return this.doc(docId).valueChanges()
  .pipe(
    mergeMap(questDoc => 
      // observable will emit then complete thanks to the "take(1)"
      const owner$ = this.userService.doc$(questDoc.owner.id).pipe(take(1));
      const collaborators$ = forkJoin(questDoc.collaborators.map(
        (userRef: DocumentReference) => 
          // same thing here 
          return this.userService.doc$(userRef.id).pipe(take(1))
        
      ));
      const categories$ = forkJoin(questDoc.categories.map(
         // and here
        (categoryRef: DocumentReference) => this.categoryService.doc$(categoryRef.id).pipe(take(1))
      ));
      const joined = forkJoin(
        owner: owner$,
        collaborators: collaborators$,
        categories: categories$
      );
      // joined.subscribe(data => console.log(data));
      return joined.pipe(
        // NEVER subscribe within a pipe, use a tap operator for side effects
        tap(data => console.log(data)),
        map(value => 
          return Object.defineProperties(questDoc, 
            qid:  value: docId ,
            owner:  value: value.owner ,
            collaborators:  value: value.collaborators ,
            categories:  value: value.categories 
          ) as Quest;
        )
      )
    )
  );

【讨论】:

非常感谢!我被这个问题困扰了很长一段时间,从来没有真正考虑过完成/底层的 Observables 仍然处于活动状态。还要感谢指向实时更新可能性的指针 (combineLatest()),因为那是我接下来要尝试的 :)

以上是关于在 AngularFire 中检索嵌套 Firestore 查询的 RxJS Observable的主要内容,如果未能解决你的问题,请参考以下文章

AngularFire2 检索不订阅更改(Firestore)

使用 AngularFire 和 Firestore 使用 Ng2 智能表显示嵌套对象

嵌套可观察订阅问题,无法取消订阅

在 ngFor 中使用带有 angularfire2 的 firebase-src 指令失败

更新 .snapshotChanges() 代码以使用 angularfire2 5.0..0 rc-8、firebase 5.0.2

避免在循环中使用嵌套的可观察订阅的正确方法是啥?