3 votes

Angular - imbrication correcte des Observables et de leurs abonnements

J'utilise angularFire2 pour extraire mes données de Firebase sous forme d'objets Observable. Voici une version simplifiée de mon code avec quelques explications ci-dessous :

this.af.getObservable(`userChats/${this.userID}/`).subscribe((recentConversations) => {
  recentConversations.forEach(conversation => {
    this.af.getObservableSortByVar(`allChats/${conversation.conversationID}/`, "lastUpdate").subscribe((conversationData) => {

      let userKey, lastMessage, lastMessageText, lastMessageSender, lastMessageDate;

      for (var i = 0; conversationData.length > i; i++) {
        switch (conversationData[i].key) {
          case "messages": {
            lastMessage = (conversationData[i][Object.keys(conversationData[i])[Object.keys(conversationData[i]).length - 1]]);
            lastMessageText = lastMessage.message;
            lastMessageSender = lastMessage.sender;
            lastMessageDate = lastMessage.date;
          }
          case "users": {
            userKey = conversationData[i].userKey;
          }
        }
      }
      this.recentChats.push(this.createConversationObject("username", userKey, lastMessageSender, lastMessageText, lastMessageDate));
    });
  });
});

Actuellement, je fais un appel à la base de données pour récupérer la liste de toutes les conversations d'un utilisateur.

  1. Je reçois un objet Observable de toutes les conversations auxquelles je m'abonne puisque je veux garder les données à jour dans la base de données.

  2. J'itère ensuite à travers l'Observable des conversations. Je dois faire un nouvel appel à la base de données pour chaque élément itéré (chaque conversation) afin d'obtenir des informations/métadonnées à son sujet (contenu, senderID, date de la conversation etc.). Ainsi, je me retrouve avec deux Observables - l'un imbriqué dans l'autre et tous deux souscrits.

  3. Après avoir obtenu le contenu/métadonnées de la conversation à partir du second observable, je pousse les métadonnées obtenues, en tant qu'objet, dans un tableau appelé "recentChats".

Cela fait l'affaire lorsque j'exécute ce bloc de code une fois (l'appel initial au début du programme). Cependant, lorsque les données de la base de données sont modifiées (le noeud 'userChat' dans la base de données ou le noeud 'allChats', ou les deux !) et que les abonnements sont activés, j'obtiens des appels multiples (répétitifs) de ce bloc de code qui inonde mon tableau avec le même résultat plusieurs fois.

Je reçois des appels inutiles alors que je veux juste un seul appel pour rafraîchir l'information.

Et ainsi, je peux voir que ma logique et ma compréhension des Observables ne sont pas correctes. Quelqu'un peut-il m'expliquer quelle serait la bonne solution pour l'exemple ci-dessus ? Comment puis-je imbriquer les abonnements aux Observables sans avoir des appels répétitifs (les mêmes) ?

1voto

SirDieter Points 216

Je pense qu'avec RxJS vous ne devriez jamais avoir à écrire votre code comme ça.

Cependant, lorsque les données de la base de données sont modifiées (le nœud "userChat" dans la base de données ou le nœud "allChats", ou les deux !) et que les abonnements sont activés, j'obtiens de multiples appels (répétitifs) de ce bloc de code qui inonde mon tableau avec le même résultat plusieurs fois.

Chaque fois que votre Observable externe émet une valeur, vous s'abonner à chaque Observable interne à nouveau . Cela signifie que vous avez pour les mêmes conversations plusieurs abonnements qui sont exécutés.

Je propose en utilisant des opérateurs pour n'avoir qu'un seul Observable et s'abonner une fois

Exemple (avec la syntaxe de RxJS 6, si vous êtes en dessous de 5.5, cela peut sembler différent) (vous devez peut-être utiliser des opérateurs différents) :

this.af.getObservable(`userChats/${this.userID}/`).pipe(
  // we map the array of conversations to an array of the (before inner) observables

  map(recentConversations =>
    recentConversations.map(conversation =>
      this.af.getObservableSortByVar(`allChats/${conversation.conversationID}/`, 'lastUpdate'))),

  // combine the observables. will emit a new value of ALL conversation data when one of the conversations changes

  switchMap(recentConversations => combineLatest(recentConversations)),
  // map each conversation to the conversation object (this is the code you had in your inner subscription)
  map(conversations =>
    conversations.map(conversationData => {
      let userKey, lastMessage, lastMessageText, lastMessageSender, lastMessageDate;

      for (let i = 0; conversationData.length > i; i++) {
        switch (conversationData[i].key) {
          case 'messages': {
            lastMessage = (conversationData[i][Object.keys(conversationData[i])[Object.keys(conversationData[i]).length - 1]]);
            lastMessageText = lastMessage.message;
            lastMessageSender = lastMessage.sender;
            lastMessageDate = lastMessage.date;
          } // don't you need a "break;" here?
          case 'users': {
            userKey = conversationData[i].userKey;
          }
        }
      }
      return this.createConversationObject('username', userKey, lastMessageSender, lastMessageText, lastMessageDate);
    }))
).subscribe(recentChats => this.recentChats = recentChats);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X