web-dev-qa-db-fra.com

Comment joindre plusieurs documents dans une requête Cloud Firestore?

J'ai une base de données Cloud Firestore avec la structure suivante:

  • utilisateurs
    • [uid]
      • nom: "Utilisateur test"
  • des postes
    • [id]
      • contenu: "Juste un article de test."
      • horodatage: (22 décembre 2017)
      • uid: [uid]

Il y a plus de données présentes dans la base de données réelle, ce qui précède illustre simplement la structure de la collection/du document/du champ.

J'ai une vue dans mon application Web où j'affiche des messages et je voudrais afficher le nom de l'utilisateur qui a posté. J'utilise la requête ci-dessous pour récupérer les messages:

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

Ce que je veux faire, c'est interroger l'objet utilisateur par l'uid stocké dans le champ uid de la publication, et ajouter le champ de nom de l'utilisateur dans l'objet chargePosts correspondant. Si je ne chargeais qu'un seul article à la fois, cela ne poserait aucun problème, attendez simplement que la requête revienne avec un objet et dans la fonction .then() effectuez une autre requête sur le document utilisateur, etc.

Cependant, étant donné que j'obtiens plusieurs documents de publication à la fois, j'ai du mal à trouver comment mapper l'utilisateur correct à la publication appropriée après avoir appelé .get() sur le document user/[uid] De chaque publication en raison de la façon asynchrone qu'ils reviennent.

Quelqu'un peut-il penser à une solution élégante à ce problème?

13
saricden

Je ferais 1 appel de doc utilisateur et l'appel de messages nécessaire.

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;
  });
}); 
7
parmigiana

Cela me semble assez simple:

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;
    });
  })
});

Si vous souhaitez empêcher le chargement d'un utilisateur plusieurs fois, vous pouvez mettre en cache le côté client des données utilisateur. Dans ce cas, je recommanderais de factoriser le code de chargement de l'utilisateur dans une fonction d'assistance. Mais ce sera une variation de ce qui précède.

17
Frank van Puffelen

ma solution comme ci-dessous.

Concept: Vous connaissez l'ID utilisateur que vous souhaitez obtenir des informations, dans votre liste de messages, vous pouvez demander un document utilisateur et l'enregistrer comme promis dans votre article. après la promesse de résolution, vous obtenez des informations sur l'utilisateur.

Remarque: je ne teste pas le code ci-dessous, mais c'est une version simplifiée de mon code.

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();
}

Remarque: afDb: AngularFirestore il est initialisé dans le constructeur (par angularFire lib)

1
Weerayut Youngklai