EDJ
EDJ

Reputation: 1023

Angular - properly nesting Observables and its subscriptions

I am using angularFire2 to extract my data from Firebase as Observable objects. Here is a simplified version of my code with some explanations to it below:

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

Currently, I am making a call to the database to retrieve a list of all conversations of a user.

  1. I receive an Observable object of all the conversations which I subscribe to since I want to keep the data up-to-date with the database.

  2. I am then iterating through the conversations' Observable. I need to make a new database call for each iterated element(each conversation) in order to obtain information/metadata about it(content, senderID, date of conversation etc). Thus, I result in having two Observables - one nested into the other which both have been subscribed to.

  3. After obtaining the contents/metadata of the conversation from the second observable, I push the metadata obtained, as an Object into an array called "recentChats".

This gets the job done when I execute this whole block of code once(the initial call at the start of the program). However, when the data in the database is modified(the 'userChat' node in the database or the 'allChats' node, or both!) and subscriptions are activated, and I get multiple (repetitive) calls of this whole block of code which floods my array with the same result multiple times.

I get unnecessary calls when I just want to have one single call to refresh the information.

And thus, I can see that my logic and understanding of Observables is not correct. Can someone explain what would be the proper solution of this example above? How can I nest Observable subscriptions without having repetitive (the same) calls?

Upvotes: 3

Views: 224

Answers (1)

SirDieter
SirDieter

Reputation: 309

I think with RxJS you should never have to write your code like that.

However, when the data in the database is modified(the 'userChat' node in the database or the 'allChats' node, or both!) and subscriptions are activated, and I get multiple (repetitive) calls of this whole block of code which floods my array with the same result multiple times.

Each time your outer Observable emits a value, you subscribe to each inner Observable again. That means you have for the same conversations multiple Subscriptions which get executed.

I suggest using operators to have only one Observable and subscribe once

Example (with RxJS 6 syntax, if you are below 5.5 it may look different) (maybe you have to use different operators):

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

Upvotes: 1

Related Questions