Problem detaching listener from Firestore nested / sub-collection

拜拜、爱过 提交于 2021-01-27 11:25:35

问题


My scenario is a chat app with the following setup in Firestore

channels (collection)
  id (doc)
    messages (collection)
    {channelObj}
  id (doc)
    messages (collection)
    {channelObj}
etc

I've successfully attached a listener to the sub collection messages although I am having trouble detaching that listener, so when I switch from and to chat channels I get duplicate entries as the listeners keep stacking.

Here's the script block from my vue file

<script>
import firestore from 'firebase/firestore'
import { mapGetters } from 'vuex'
import SingleMessage from './SingleMessage'
import MessageForm from './MessageForm'

export default {
  name: 'messages',
  components: {
    SingleMessage,
    MessageForm,
  },
  data() {
    return {
      channelsRef: firebase.firestore().collection('channels'),
      messages: [],
      channel: '',
      unsubscribe: null
    }
  },
  computed: {
    ...mapGetters(['currentChannel']),
  },
  watch: {
    currentChannel: async function(newValue, oldValue) {
      this.messages = []
      oldValue && 
        await this.detachListeners(newValue, oldValue)
      await this.unsubscribe
      await this.timeout(2000)
      await this.addListeners(newValue)
    },
  },
  methods: {
    addListeners(newValue) {
      this.channelsRef
        .doc(newValue.id)
        .collection('messages')
        .onSnapshot(snapshot => {
          snapshot.docChanges().forEach(change => {
            if (change.type == 'added') {
              let doc = change.doc
              this.messages.push({
                id: doc.id,
                content: doc.data().content,
                timestamp: doc.data().timestamp,
                user: doc.data().user,
              })
            }
          })
        })
      //
      console.log('[addListeners] channel:', newValue.id)
    },
    detachListeners(newValue, oldValue) {
      this.unsubscribe = 
      this.channelsRef
        .doc(oldValue.id)
        .collection('messages')
        .onSnapshot(() => {})
      //
      console.log('[detachListeners] channel:', oldValue.id)
    },
    timeout(ms) {
      console.log('waiting...')
      return new Promise(resolve => setTimeout(resolve, ms));
    },
  },
}
</script>

As you can see I am using a Vue watcher to monitor when the channel changes. To clarify, the console.log are firing with the correct doc ids so it should be targeting correctly. I tried using asynchronous code to await the detach but that does not work.

The docs advising saving the detach code to a variable and calling that, which I am now doing in my watch block. When console logging that it says this

ƒ () {
            asyncObserver.mute();
            firestoreClient.unlisten(internalListener);
        }

So I am a bit lost here, seems I am targeting the right collection with the right method for unlistening ... any other steps I can take to debug?


回答1:


You have to store in data the function returned by the onSnapshot() method and call this function in order to detach the listener.

In your existing code you are indeed declaring an unsubscribe object in data but you are not correctly assigning to it the function returned by the onSnapshot() method (you should do that in the addListeners() method) and you are not calling it correctly (you do this.unsubscribe instead of this.unsubscribe()).

I've not reproduced your full case, since it implies a Vuex store and some extra components but you will find below a similar code that demonstrates how it works (my settings are a bit different than yours -I use require("../firebaseConfig.js"); and fb.db.collection(channel)- but you'll easily get the philosophy!):

<template>
  <div>
    <input v-model="currentChannel" placeholder="Enter Current Channel">
    <p>CurrentChannel is: {{ currentChannel }}</p>
    <div class="messagesList">
      <li v-for="m in messages">{{ m.name }}</li>
    </div>
  </div>
</template>

<script>
const fb = require("../firebaseConfig.js");
export default {
  data() {
    return {
      messages: [],
      currentChannel: null,
      listener: null    //We store the "listener function" in the object returned by the data function
    };
  },
  watch: {
    currentChannel: function(newValue, oldValue) {
      this.messages = [];
      if (this.listener) {
        this.listener();  //Here we call the "listener function" -> it detaches the current listener
        this.addListeners(newValue);
      } else {
        this.addListeners(newValue);
      }
    }
  },
  methods: {
    addListeners(channel) {
      this.listener = fb.db.collection(channel).onSnapshot(snapshot => {
        snapshot.docChanges().forEach(change => {
          if (change.type == "added") {
            let doc = change.doc;
            this.messages.push({
              id: doc.id,
              name: doc.data().name
            });
          }
        });
      });
    }
  }
};
</script>

<style>
.messagesList {
  margin-top: 28px;
}
</style>

So, if we try to apply this approach to your code, the modified code would be as follows:

<script>
import firestore from 'firebase/firestore'
import { mapGetters } from 'vuex'
import SingleMessage from './SingleMessage'
import MessageForm from './MessageForm'

export default {
  name: 'messages',
  components: {
    SingleMessage,
    MessageForm,
  },
  data() {
    return {
      channelsRef: firebase.firestore().collection('channels'),
      messages: [],
      channel: '',
      unsubscribe: null
    }
  },
  computed: {
    ...mapGetters(['currentChannel']),
  },
  watch: {
    currentChannel: function(newValue, oldValue) {
          this.messages = [];
          if (this.unsubscribe) {
            this.unsubscribe(); 
            this.addListeners(newValue);
          } else {
            this.addListeners(newValue);
          }
    }
  },
  methods: {
    addListeners(newValue) {
      this.unsubscribe = this.channelsRef
        .doc(newValue.id)
        .collection('messages')
        .onSnapshot(snapshot => {
          snapshot.docChanges().forEach(change => {
            if (change.type == 'added') {
              let doc = change.doc
              this.messages.push({
                id: doc.id,
                content: doc.data().content,
                timestamp: doc.data().timestamp,
                user: doc.data().user,
              });
            }
          });
        });
      console.log('[addListeners] channel:', newValue.id)
    }
  }
}
</script>


来源:https://stackoverflow.com/questions/55682344/problem-detaching-listener-from-firestore-nested-sub-collection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!