0

I am subscribing to get user chats and then for each chat, I am subscribing to two other observables.

get_user_chat(chat) {
    let user = this.firebase.get_user_by_uid(chat.key).snapshotChanges();
    let chatInfo = this.firebase.get_single_chat(chat.payload.val()).snapshotChanges();
    return Observable.forkJoin(user.take(1), chatInfo.take(1));
}

getChats() {
//start loading
let loading = this.loadingCtrl.create();
loading.present();

//get chats
this.firebase.get_user_chats().snapshotChanges().takeUntil(this.ngUnsubscribe).subscribe(chats => {
  chats.forEach(chat => {
    this.get_user_chat(chat).takeUntil(this.ngUnsubscribe).subscribe(userChat => {
      this.userChat = userChat;
      this.user = this.userChat[0];
      this.chat = this.userChat[1];

      this.chats.push({
        firstName: this.user.payload.val().firstName,
        imageUrl: this.user.payload.val().imageUrl,
        uid: this.user.key,
        chatId: chat.payload.val(),
        last_message: this.chat.payload.val().last_message.message,
        type: this.chat.payload.val().last_message.type
      })
    })
  })
  //dimiss loading
  loading.dismiss();
}, err => {
  //dimiss loading
  loading.dismiss();
})

}

Now for the front end, I am displaying the data like this:

<ion-content>
  <ion-list class="chat-list" *ngIf="(chats)?.length>0">
    <a ion-item detail-none *ngFor="let c of chats" (click)="toChat(c.chatId, c.uid)">
      <ion-thumbnail item-start>
        <img-loader class="img-logo" src={{c.imageUrl}} fallbackUrl="assets/img/placeholder-person.png" useImg></img-loader>
      </ion-thumbnail>
      <h2>{{c.firstName}}</h2>
      <h3 *ngIf="c.type!='img'">{{c.last_message}}</h3>
      <img class="img-placeholder" *ngIf="c.type=='img'" src="assets/img/placeholder-image.png">
      <!-- <ion-icon item-right name="ios-arrow-forward"></ion-icon> -->
    </a>
  </ion-list>
  <ion-card *ngIf="(chats)?.length==0">
    <ion-card-content>
      You don't have any chats.
    </ion-card-content>
  </ion-card>
</ion-content>

This is working except that chat last message won't update automatically in the front end, I understand why this isn't working, I am pushing the observer into an array which loses the function to listen to changes, but I couldn't figure out how I can listen to changes without pushing the objects from two different subscribers and display them correctly. I basically have to reload the chat list every time to see the change.

It's better explained with pictures:

Last message for Holly is 'a' Last message for Holly is a

I send another message to Holly, the last message should now be 'B' I send another message, the last message should now be B

But last message won't update without refreshing the page, it's not listening to changes because I am pushing the observable into an array But last message won't update without refreshing the page

Firebase realtime database structure for 1:1 chat

User 1: enter image description here

User 2: enter image description here

Chat Room: enter image description here

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Mindless
  • 2,352
  • 3
  • 27
  • 51

1 Answers1

1

I highly recommend modeling each chat room as a collection in Firestore. That way, if you have a chat room/collection for the chat between Brendan and Holly, you're only reading from a single collection, instead of two. That also makes it much easier to keep the messages in the correct order, since you can use a single query to get them.

For an example of this see Best way to manage Chat channels in Firebase. While this question is about Realtime Database, the same logic can be applied to Cloud Firestore.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I think i've done something similar, user have chats with the chatting user id as the key and chat room key as the value, however you still need to get user info from the "user" and chat info from "chat", because this is just the key and value, no other info. Otherwise I have to put these infos(chatting firstName, last message) into user chats, and I will have to update several places when user either change his firstname or last message changes, this is fine in in firebase I understand, but it becomes so much more complicated, I've updated my question. – Mindless Feb 05 '19 at 02:56
  • This kind of structure actually confuses me quite a lot, how are you suppose to get user info without making another call to get the user info such as name, gender etc, the list only contains the user ids with true as the value. – Mindless Feb 05 '19 at 03:04
  • If you have user info, and chat message, you'll typically keep two top-level lists: one for the user infos, and one for the chat messages. You'll need to do a client-side join, to get the user info. You can also duplicate some of the user info into each chat messages, or set up a separate node with all metadata for the room in yet another top-level list. But the most important thing is that your chat messages are stored as a single list, so that you're not trying to load the messages from each user separately. – Frank van Puffelen Feb 05 '19 at 03:29
  • From scanning your data model it seems that you're trying to show the most recent message in each chat room. That should be done with either a `limitToLast()` listener on each chat room, or by keeping the last message for each room in the "room metadata" too. The latter is more complex on write operations, but makes the read a lot simpler, since you only need one listener and thus don't need your (failing) merge of two lists. – Frank van Puffelen Feb 05 '19 at 04:38
  • Yes this is my original thought, to keep last message and user info in the room meta data along with the room key. Like you've said, it makes a more complex write operation, manageable between 1:1 chat, but for group chats, if you keep last message in the room meta data, you could be writing that at 20 different places. – Mindless Feb 05 '19 at 04:47
  • Do you know where I can find an example, most of chatting app example doesn't really care about showing the last message in the list. – Mindless Feb 05 '19 at 04:49
  • 1
    Recommending an off-site resource would be off-topic anyway, which is why I gave you the most-used options in my last comment. – Frank van Puffelen Feb 05 '19 at 05:00