3
votes

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?

1
If you can retain the value of i and then only loop through the new indexes in the conversationData array. - Pal Singh
@PalSingh I am not sure what you mean. If I store i, say, as a global variable, outside this block of code, I will not get any new information from the conversationsData after I call this method once. And I want to be up-to-date with the database, just prevent the multiple repetitive calls from happening. I am certain that the problem in my case is associated with having two nested subscriptions which causes the same code to be executed multiple times when the database is modified just once. - EDJ
You should try returning your conversation first in a separate class and maybe push it into an array and then iterate over the results. Also try using map in place of forEach. - Papa Kojo
@PapaKojo I can try to return the conversation into a separate method or class, but wouldn't this have the same result in the end? Since when the data in firebase is modified, subscribe will be triggered and it will just repeat the whole code multiple times? I was wondering if maybe there is a way to remove the nested subscribe, but preserving the updating (if data is modified in Firebase). - EDJ

1 Answers

1
votes

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