【问题标题】:What's the best way to get data from collections nested in documents in Firestore?从嵌套在 Firestore 文档中的集合中获取数据的最佳方法是什么?
【发布时间】:2018-04-05 11:38:12
【问题描述】:

我正在浏览 Firestore 文档和指南。下面我的代码示例使用 AngularFire2。

让我们考虑一个类似于此处提供的示例的“聊天”集合:https://firebase.google.com/docs/firestore/manage-data/structure-data

他们推荐这种结构,但我看不出他们在哪里讨论有效地获取所有数据。

每个聊天文档都有属性、成员集合和消息集合:

  • chatsCollection
    • 聊天文档
      • [在此处插入聊天数据字段]
      • membersCollection
        • 成员文件
          • [在此处插入成员数据字段]
      • messagesCollection
        • 消息文档
          • [在此处插入消息数据字段]

Firestore 查询很浅,有时可能很棒。我的理解是,没有固定的方法可以深入查询并获取嵌套集合。那么,最好的做法和最轻松的方法是什么?

目前,我正在检索快照并将其映射到具有 ID 的对象,并将嵌套的集合数据添加到具有附加查询和映射的父文档数据中,我对我的方法不满意,甚至可以做得更快具有非规范化的 Firebase 结构。

此代码示例只是映射成员,添加回消息是另一回事...

getChatsFromFirestore() {
    this.chats = this.dataSvc.getChatsFromFirestore().snapshotChanges()
      .map(chatSnaps => {
        return chatSnaps.map(chat => {
          const chatData = chat.payload.doc.data();
          const chatId = chat.payload.doc.id;
          return this.dataSvc.getMembersForChat(chatId)
            .snapshotChanges()
            .map(memberSnaps => {
              return memberSnaps.map(member => {
                const data = member.payload.doc.data();
                const id = member.payload.doc.id;
                return { id, ...data }
              });
            })
            .map(members => {
              return { chatId, ...chatData, members: members };
            });
        })
      })
      .flatMap(chats => Observable.combineLatest(chats));
  }

从服务中:

getChatsFromFirestore() {
  return this.fsd.collection<any>('chats');
}
getChatFromFirestoreById(id: string) {
  return this.fsd.doc(`chats/${id}`);
}
getMembersForChat(chatId) {
  return this.getChatFromFirestoreById(chatId).collection('members');
}

【问题讨论】:

  • 我想这里有一个论点是根据实时数据库的流程复制和非规范化数据(类似于this answer),但我也希望有一个不同/更好的流程来加入数据在 Firestore 中。
  • 好点,在这种情况下,我确实对数据进行了欺骗和破坏。例如,每个聊天都包含其成员,这些成员具有与 UID 匹配的文档 ID,该 UID 可用于获取该成员的所有数据,但还包含用户名。每条单独的消息还包含一个“authorName”字段和一个“sentBy”字段再次保存该用户的 UID……仍然能够在不递归调用 DB 或至少不那么冗长的情况下获取聊天室文档下的所有嵌套数据代码会很酷。
  • 如果将id以chatDocument保存为字段,则可以使用.valueChanges()获取id。这比.snapshotChanges() 快。
  • 如何只为用户获取相关的聊天文档?每个用户都会看到每个聊天记录吗?
  • 这只是 Firebase 文档中数据的一个相关示例。为用户获取相关的聊天文档将遵循稍微不同的路径,但仍需要嵌套的顺序数据库查询。可能在其他情况下,我希望像示例中一样一次获取文档列表及其所有嵌套数据。

标签: angular typescript firebase angularfire2 google-cloud-firestore


【解决方案1】:

您发布的方法似乎可行,对于大型聊天应用程序,您可能不想跟踪每个聊天室中发生的每一个事件,因为这可能是大量数据。相反,最好只订阅需要的内容并使用云功能和云消息处理定期更新。

通过使用辅助函数 observeCollection 以及小代码重组,它将清理服务并为每个聊天室创建可观察对象,这些聊天室在订阅之前将处于非活动状态。

class Service {
    // db is plan firestore / no angularfire
    db: firebase.firestore.Firestore;

    loadChatrooms() {
        const chatsRef = this.db.collection('chats');
        return observeCollection(chatsRef)
            .pipe(
                map(chats => {
                    return chats.map(chat => {
                        return {
                            chat,
                            members$: this.observeCollection(chat.ref.collection('members')),
                            messages$: this.observeCollection(chat.ref.collection('messages')),
                        };
                    })
                }),
            );
    }

    // Takes a reference and returns an array of documents
    // with the id and reference
    private observeCollection(ref) {
        return Observable.create((observer) => {
            const unsubscribeFn = ref.onSnapshot(
                snapshot => {
                    observer.next(snapshot.docs.map(doc => {
                        const data = doc.data();
                        return { 
                            ...doc.data(),
                            id: doc.id,
                            ref: doc.ref
                        };
                    }));
                },
                error => observer.error(error),
            );

            return unsubscribeFn;
        });
    }
}

在应用程序中,您只能观察当前选择的聊天室成员和消息,这将保存数据。 由于这篇文章被标记为 Angular 异步管道将有助于通过自动手动订阅进行切换。

在您的组件中:

this.currentChat$ = combineLatest(
  service.loadChatrooms(),
  currentlySelectedRoomId
).pipe(
    map(([chats, selectedRoomId]) => {
        return chats.first(chat => chat.id === selectedRoomId)
    })
);

在您的模板中:

<div *ngIf="currentChat$ as currentChat">
    {{ currentChat.name }}

    <div *ngIf="currentChat.members$ as members">
        <div *ngIf="let member of members">
          {{ member.name }}
        </div> 
    </div> 

    <div *ngIf="currentChat.messages$ as messages">
        <div *ngIf="let message of messages">
          {{ message.content }}
        </div> 
    </div> 
</div>

【讨论】:

  • 如果答案朝着正确的方向发展,我们很乐意进一步讨论。
猜你喜欢
  • 2021-05-02
  • 1970-01-01
  • 2019-03-14
  • 1970-01-01
  • 2019-11-29
  • 1970-01-01
  • 1970-01-01
  • 2010-09-25
  • 2020-04-29
相关资源
最近更新 更多