Vue.js でFirestoreからMessagesコレクションを取り出すときにソートしたい
FirebaseのCloud Firestoreを用いて、リアルタイムに反映される簡単なチャットアプリをVue.jsで実装したいと思っています。
Firestoreにはusers
コレクションとmessages
コレクションがあり、それぞれのドキュメントには
{name: 名前, photoURL: プロフィール画像url}
, {userID: 投稿したuserドキュメントのID, message: 本文, createdAt: 投稿時間}
というデータを保存しています。
実際にチャットを送信、表示されるコンポーネントにて
firebase.firestore().collection('messages').orderBy('createdAt').onSnapshot(snapshot => {snapShot.changes().forEach(...)})
というようにmessagesコレクションをcreatedAtでソートして取得し、forEachの中でそれぞれのmessageのuserIdから更にuserを
firebase.firestore().collection('users').doc(message.data().userId).get().then(author => {})
で取得してからコンポーネントのdataのmessages[]に
this.messages.push({
key: message.id,
userName: author.data().name,
userPhoto: author.data().photoURL,
message: message.data().message
});
と追加し、これをtemplate
<div v-for="{ key, userName, userPhoto, message } in messages" :key="key">
</div>
の中で表示しています。
期待する結果としては、全てのmessageドキュメントを投稿時間順に、その投稿者のnameと画像userPhotoともに一覧表示されてほしいのですが、
結果としてはユーザーごとのmessageがまとめて表示(その中では投稿時間順になっていますが)されてしまいます。
ユーザーAとBの投稿が数件ずつあったとして、全ての投稿が投稿時間順に並ぶのではなく、
ユーザーAでページを開くとまずユーザーAのした投稿が投稿時間順に上から並び、その下からユーザーBのした投稿が投稿時間順にならびます。
質問させていただきたいのは、最初にコレクションでorderByしているのになぜユーザーごとのmessageがまとまってしまうのか。そしてその解決方法です。
原因を調べていてforEachの中でデータ取得しているから非同期で処理されてしまっていて...?というところまできましたが、それではユーザー順になる理由がわからず...
当方プログラミングを勉強し始めて間もなく、所々常識となっていそうな知識が抜けているかもしれないのでこの質問の内容に際してそういったところがあればご指摘いただけるとありがたいです。
また、そもそもこの目的ならデータ構造からして間違っているというようなことがあればそれもご教示いただきたいです。何卒よろしくおねがいします。
実際のコードは一部省略させていただきますが以下のようになります
<template>
<div class='chat'>
<h2>チャット</h2>
<transition-group name="chat" tag="div" class="message-list container">
<div v-for="{ key, userName, userPhoto, message } in messages" :key="key" class="message">
<div class="message-user">
<div class="message-img user-profile-img">
<img v-if='userPhoto' :src="userPhoto">
<img v-else :src="defaultImgURL" alt="">
</div>
<div class="message-name">{{ userName }}</div>
</div>
<div class="message-text-box">
<div class='message-text'>{{ message }}</div>
</div>
</div>
</transition-group>
<form action="" @submit.prevent="sendMessage" class="message-form">
<textarea
v-model="input"
:disabled="!userStatus"
@keydown.enter.exact.prevent="sendMessage"
class='form-control'></textarea>
<button type="submit" :disabled="!userStatus" class="btn">送信する</button>
</form>
</div>
</template>
<script>
import firebase from 'firebase';
import authUser from '../authUser.js'; //authでログインしているユーザー
export default {
name: 'chat',
data() {
return {
messages: [], // 取得したメッセージを入れる配列
input: '', // 入力したメッセージ
defaultImgURL: 'userPhotoが登録されていないときに表示される画像'
}
},
created() {
authUser.onAuth();
// messagesコレクションを取得・追加を監視してループでsetMessage()にわたす
this.dbMessages.orderBy('createdAt').onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added' ) {
const message = change.doc;
this.setMessage(message)
}
});
});
},
computed: {
currentUser() {
return this.$store.getters.currentUser;
},
db() {
return firebase.firestore()
},
dbMessages() {
return this.db.collection('messages');
},
dbUsers() {
return this.db.collection('users');
}
},
methods: {
// メッセージをDBに登録
sendMessage() {
if(this.input === '') {
return
}
this.dbMessages.add({
userId: this.currentUser.uid, //投稿するユーザー
message: this.input.trim(), // 本文
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
.then(doc => {
this.input = '';
})
.catch(error => {
console.log(error);
})
},
// messageのuserIdから投稿したユーザーを取得しそのnameとphotoURLをdataのmessages[]に入れる
setMessage(message) {
this.dbUsers.doc(message.data().userId).get()
.then(author => {
this.messages.push({
key: message.id,
userName: author.data().name,
userPhoto: author.data().photoURL,
message: message.data().message
});
})
.catch(error => {
console.log(error);
});
},
}
}
</script>