如何在Cloud Firestore查询中加入多个文档?

时间:2017-12-22 21:36:03

标签: javascript firebase google-cloud-firestore

我有一个具有以下结构的Cloud Firestore DB:

  • 用户
    • [UID]
      • 名称:“测试用户”
  • 帖子
    • [ID]
      • 内容:“只是一些测试帖。”
      • 时间戳:( 2017年12月22日)
      • uid:[uid]

实际数据库中存在更多数据,上面仅说明了集合/文档/字段结构。

我在我的网络应用中有一个视图,我正在显示帖子,并希望显示发布的用户的名称。我正在使用以下查询来获取帖子:

let loadedPosts = {};
posts = db.collection('posts')
          .orderBy('timestamp', 'desc')
          .limit(3);
posts.get()
.then((docSnaps) => {
  const postDocs = docSnaps.docs;
  for (let i in postDocs) {
    loadedPosts[postDocs[i].id] = postDocs[i].data();
  }
});

// Render loadedPosts later

我想要做的是通过post的uid字段中存储的uid查询用户对象,并将用户的name字段添加到相应的loadedPosts对象中。如果我一次只加载一个帖子就没问题,只需等待查询返回一个对象,然后在.then()函数中再向用户文档发出查询,依此类推。

但是因为我一次收到多个帖子文档,所以在每个帖子的.get()文档上调用user/[uid]之后,我很难弄清楚如何将正确的用户映射到正确的帖子由于他们返回的异步方式。

有人能想到这个问题的优雅解决方案吗?

6 个答案:

答案 0 :(得分:16)

对我来说似乎相当简单:

let loadedPosts = {};
posts = db.collection('posts')
          .orderBy('timestamp', 'desc')
          .limit(3);
posts.get()
.then((docSnaps) => {
  docSnaps.forEach((doc) => {
    loadedPosts[doc.id] = doc.data();
    db.collection('users').child(doc.data().uid).get().then((userDoc) => {
      loadedPosts[doc.id].userName = userDoc.data().name;
    });
  })
});

如果要阻止多次加载用户,可以缓存用户数据客户端。在这种情况下,我建议将用户加载代码分解为辅助函数。但它可以是上述的变体。

答案 1 :(得分:8)

我会进行1个用户文档调用和所需的帖子调用。

let users = {} ;
let loadedPosts = {};
db.collection('users').get().then((results) => {
  results.forEach((doc) => {
    users[doc.id] = doc.data();
  });
  posts = db.collection('posts').orderBy('timestamp', 'desc').limit(3);
  posts.get().then((docSnaps) => {
    docSnaps.forEach((doc) => {
    loadedPosts[doc.id] = doc.data();
    loadedPosts[doc.id].userName = users[doc.data().uid].name;
  });
}); 

答案 2 :(得分:1)

我的解决方案如下。

概念:您知道要获取信息的用户ID,在帖子列表中,您可以请求用户文档并将其保存为您的帖子项目中的承诺。在承诺解决后,您将获得用户信息。

注意:我不测试下面的代码,但它是我的代码的简化版本。

let posts: Observable<{}[]>;  // you can display in HTML directly with  | async tag
this.posts = this.listenPosts()
         .map( posts => {
           posts.forEach( post => {
                post.promise = this.getUserDoc( post.uid )
                               .then( (doc: DocumentSnapshot) => {
                                  post.userName = doc.data().name;
                               });
           }); // end forEach
           return posts;
         });

// normally, i keep in provider
listenPosts(): Observable<any> {
  let fsPath = 'posts';
  return this.afDb.collection( fsPath ).valueChanges();
}

// to get the document according the user uid
getUserDoc( uid: string ): Promise<any> {
   let fsPath = 'users/' + uid;
   return this.afDb.doc( fsPath ).ref.get();
}

注意:afDb:AngularFirestore它在构造函数中初始化(通过angularFire lib)

答案 3 :(得分:1)

在尝试多种解决方案后,我使用 RXJS combineLatest, take 运算符完成了它。使用 map 函数我们可以组合结果。

可能不是最佳解决方案,但它可以解决您的问题。

combineLatest(
    this.firestore.collection('Collection1').snapshotChanges(),
    this.firestore.collection('Collection2').snapshotChanges(),
    //In collection 2 we have document with reference id of collection 1
)
.pipe(
    take(1),
).subscribe(
    ([dataFromCollection1, dataFromCollection2]) => {

        this.dataofCollection1 = dataFromCollection1.map((data) => {
            return {
                id: data.payload.doc.id,
                ...data.payload.doc.data() as {},
            }
            as IdataFromCollection1;
        });

        this.dataofCollection2 = dataFromCollection2.map((data2) => {
            return {
                id: data2.payload.doc.id,
                ...data2.payload.doc.data() as {},
            }
            as IdataFromCollection2;
        });

        console.log(this.dataofCollection2, 'all feeess');

        const mergeDataFromCollection =
            this.dataofCollection1.map(itm => ({
                payment: [this.dataofCollection2.find((item) => (item.RefId === itm.id))],
                ...itm
            }))

        console.log(mergeDataFromCollection, 'all data');
    },

答案 4 :(得分:0)

如果您想加入 observables 而不是 promises,请使用 combineLatest。以下是将用户文档加入发布文档的示例:

  getPosts(): Observable<Post[]> {
    let data: any;
    return this.afs.collection<Post>('posts').valueChanges().pipe(
      switchMap((r: any[]) => {
        data = r;
        const docs = r.map(
          (d: any) => this.afs.doc<any>(`users/${d.user}`).valueChanges()
        );
        return combineLatest(docs).pipe(
          map((arr: any) => arr.reduce((acc: any, cur: any) => [acc].concat(cur)))
        );
      }),
      map((d: any) => {
        let i = 0;
        return d.map(
          (doc: any) => {
            const t = { ...data[i], user: doc };
            ++i;
            return t;
          }
        );
      })
    );
  }

此示例将集合中的每个文档连接起来,但如果您只想将一个文档连接到另一个文档,则可以简化此操作。

这假设您的post 文档有一个 user 变量和文档的 userId

J

答案 5 :(得分:-2)

我将只执行一个userdoc查询和所需的postsdoc调用。

Let users={} ;
let loadedPosts = {};
db.collection('users').get().then((results) =>{
Results.forEach( (doc) =>
users[doc.id]=doc.data();
}) 
posts=db.collection('posts') .orderBy('timestamp', 'desc') 
posts.get().then((docSnaps)=>{
docSnaps.forEach((doc)=>{
loadedPosts[doc.id] = doc.data();
loadedPosts[doc.id].userName = users[doc.data().uid].name});
})