I'm creating a single page application with javascript by using the firebase firestore as database.
I've managed to get all my documents listed where I call for all of them. Each document has an href to the detail page. But on the detailpage, it looks like I don't have any data from my document.
I want to call the title from the document to show as an h1 but noting renders and I don't have any errors in my console..
Anyone who can help me?
My code:
This is to get all the documents (who are events)
// Get events
import firebase from 'firebase/app';
import 'firebase/firestore';
const Events = {
getAll: async () => {
// get firestore
const db = firebase.firestore();
// define query
const query = db.collection('events');
// query snapshot
const querySnapshot = await query.get();
// loop over all documents
return querySnapshot.docs.map((doc) => (
{
...doc.data(),
id: doc.id,
}
));
},
// get the data from a detailpage by the ID
getById: async (id) => {
const db = firebase.firestore();
const event = await (await (db.collection('events').doc(id).get())).data();
return event;
},
};
export default Events;
My component to render all documents as a list
import Component from '../lib/Component';
import Elements from '../lib/Elements';
import Router from '../Router';
import Events from '../lib/Events';
class EventsComponent extends Component {
constructor() {
super({
name: 'events',
model: {
events: [],
},
routerPath: '/events',
});
this.eventsLoaded = false;
}
// Get the events one by one, make them an href to their detail page and show the name of the
event
loadEvents() {
if (!this.eventsLoaded) {
Events.getAll().then((data) => {
this.model.events = data.map((event) => ({
href: `${Router.getRouter().link('/event')}/${event.id}`,
textContent: event.title,
}));
});
this.eventsLoaded = true;
}
}
render() {
const { events } = this.model;
// create home container
const eventsContainer = document.createElement('div');
// Load events
this.loadEvents();
// Check if there are any events
if (events.length === 0) {
eventsContainer.innerHTML = 'There are no events planned at the moment';
} else {
eventsContainer.appendChild(
Elements.createList({
items: this.model.events,
}),
);
}
return eventsContainer;
}
}
export default EventsComponent;
My detail page
// Event Component
import Component from '../lib/Component';
import Elements from '../lib/Elements';
import Events from '../lib/Events';
class EventComponent extends Component {
constructor() {
super({
name: 'event',
model: {
event: [],
},
routerPath: '/event/:id',
});
this.eventLoaded = false;
}
// Set the model loading to true when the id page is founded
loadEvent(id) {
if (!this.eventLoaded) {
this.eventLoaded = true;
Events.getById(id).then((data) => {
this.model.event = data;
});
}
}
render() {
const { event } = this.model;
// create event overview container
const eventContainer = document.createElement('div');
// Check for existing events and return the events.
// Create an h1 with the name of the event as title
if (!event) {
this.loadEvent(this.props.id);
} else {
eventContainer.appendChild(
Elements.createHeader({
textContent: event.title,
}),
);
console.log(this.model.event);
}
return eventContainer;
}
}
export default EventComponent;
You have model set as an array insted of an object in you detail page.
constructor() {
super({
name: 'event',
model: {
event: [],
},
routerPath: '/event/:id',
});
this.eventLoaded = false;
}
When you try to access your event you are trying to get .title from an array insted of an object.
Elements.createHeader({
textContent: event.title,
}),
I would map the event field by field { "field" : 0, "field2": ""} but you may find a way to make it less verbose
Related
I have a JS Class that generates some content then I have some event listeners listening to the newly-created content, right now it looks like that:
https://jsfiddle.net/2fa6k4jz/
const articles = [{ text: "abc" },{ text: "xyz" }]
class Page {
constructor(articles) {
this.articles = articles;
};
render() {
this.articles.forEach((article) => {
const art = document.createElement("article");
art.textContent = article.text;
document.body.appendChild(art);
})
}
}
const blog = new Page(articles);
blog.render();
document.querySelectorAll("article").forEach((article) => {
article.addEventListener("click", (e) => alert(e.target.textContent));
});
Is there a way to move my eventListener logic to the inside of the Class? I have tried moving const articles = document.querySelectorAll("article") to constructor but it returs null as the articles aren't already there...
Thanks for any hints!
Yes u can do move ur event listener logic to the inside class by adding ur addEventListner method to the class Page.
try this :
const articles = [{ text: "abc" }, { text: "xyz" }];
class Page {
constructor(articles) {
this.articles = articles;
}
render() {
this.articles.forEach((article) => {
const art = document.createElement("article");
art.textContent = article.text;
document.body.appendChild(art);
});
}
addEventListenerToArticles() {
document.querySelectorAll("article").forEach((article) => {
article.addEventListener("click", (e) => alert(e.target.textContent));
});
}
}
const blog = new Page(articles);
blog.render();
blog.addEventListenerToArticles();
You can attach the eventListener inside your render function
{ text: "abc" },
{ text: "xyz" }
]
class Page {
constructor(articles) {
this.articles = articles;
};
render() {
this.articles.forEach((article) => {
const art = document.createElement("article");
art.textContent = article.text;
art.addEventListener("click", (e) => alert(e.target.textContent));
document.body.appendChild(art);
})
}
}
const blog = new Page(articles);
blog.render();
I dont know much about vue/bootstrap and reading docs does not help me to understand how it all works.
How to open a modal that is created after the page was loaded. From user input. User clicks button then the modal loads into a list prop and then renders into DOM and then it opens up.
Im at the point where i created event when user clicks the button that loads the modal into the list, but how do you catch the "modal has been added to DOM" event and then you can use getElementByID to instantiate the modal and then use .show() to show it?
I can see that the card that supposed to render on the page loads/renders, but the method get null. Im guessing that the method runs before the page/DOM has been re-rendered. So how do you run another method with parameter of sorts after the custom event that added the item to list has been triggered?
The code is too big and convoluted to post. But if need be i could try to trim it down, but its a mess.
App.vue
<template>
<div class="container-center">
<AnimeList />
</div>
</template>
AnimeList.vue
<template>
<div class="containerlist">
<AnimeCardModal
v-for="anime in animeList"
:anime="anime"
#checkAnimeListForRelatedEvent="checkAnimeListForRelated"
/>
</div>
</template>
<script setup>
import { defineComponent } from "vue";
import AnimeCardModal from "./AnimeCardModal.vue";
import axios from "axios";
</script>
<script>
export default defineComponent({
name: "AnimeList",
data() {
return {
animeList: [],
limit: 30,
page: 1,
reachedEnd: false,
};
},
methods: {
async getAnimeLsit() {
const res = await axios.get("/api", {
params: { page: this.page, limit: this.limit },
});
this.animeList = res.data.data;
this.page = res.data.next.page;
this.limit = res.data.next.limit;
},
async getNextBatch() {
let bottomOfWindow =
document.documentElement.scrollTop + window.innerHeight ===
document.documentElement.offsetHeight;
if (bottomOfWindow && !this.reachedEnd) {
const res = await axios.get("/api", {
params: { page: this.page, limit: this.limit },
});
res.data.data.map((item) => {
this.animeList.push(item);
});
if (!res.data.next) {
this.reachedEnd = true;
} else {
this.page = res.data.next.page;
this.limit = res.data.next.limit;
}
}
},
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
}
}
// Add the anime to the list
},
},
created() {
window.addEventListener("scroll", this.getNextBatch);
},
deactivated() {
window.removeEventListener("scroll", this.getNextBatch);
},
async mounted() {
await this.getAnimeLsit();
},
components: {
AnimeCardModal,
},
});
</script>
Here is the method that gets triggered by the user click event where it loads the Not in main list data and should render on page/DOM.
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]); <--------------------------------------
console.log("added to list");
}
}
// Add the anime to the list
},
The added item is a modal with element id. I want to instantiate this element as new Modal() and open it with .show().
But the i get error that the element does not exist = null and i cant get it, but i can see it on screen.
EDIT:1
Ok so like as per usual, once i post on SO i find an answer to my problem, but it turns into another problem.
SO to get the rendered element i used this:
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
this.$parent.$nextTick(() => { <----------------------
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.show();
}
}else{
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.show();
}
// Add the anime to the list
},
It works, but now the modals overlay each other, seems like its not working like when you add the attributes to the card element that opens modal:
:data-bs-target="'#anime-card-modal-' + anime.id"
data-bs-toggle="modal"
Is there a way to get the same effect from method as with these attributes?
I want to open a modal, by clicking an element with those attributes, then when i click another element with them attributes (different target id) it closes previously opened modal and opens the target modal.
Alright, i found a solution, works pretty good.
Instead of using myModal.show() i used myModal.toggle("anime-card-modal-" + animeID) and the else statement is not needed in the event method:
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
this.$parent.$nextTick(() => {
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.toggle("anime-card-modal-" + animeID) <---------------
}
}
// Add the anime to the list
},
See EDIT Below
I have massively improved over my last question, but I am stuck again after some days of work.
Using Vue, Vue-router, Vuex and Vuetify with the Data on Googles Could Firestore
I want to update my data live, but i cannot find a way to do this.
Do i need to restructure, like moving products and categories into one collection?
Or is there any bind or query magic to get this done.
As you can see below, it loads the data on click quite well, but I need the live binding 'cause you could have the page open and someone could sell the last piece (amountLeft = 0). (And a lot of future ideas).
My data structure is the following:
categories: {
cat_food: {
name: 'Food'
parentCat: 'nC'
},
cat_drinks: {
name: 'Food'
parentCat: 'nC'
},
cat_beer: {
name: 'Beer'
parentCat: 'cat_drinks'
},
cat_spritz: {
name: 'Spritzer'
parentCat: 'cat_drinks'
},
}
products: {
prod_mara: {
name: 'Maracuja Spritzer'
price: 1.5
amountLeft: 9
cat: ['cat_spritz']
},
prod_capp: {
name: 'Cappuccino'
price: 2
cat: ['cat_drinks']
},
}
The categories and the products build a tree. The GIF shows me opening the categories down to show a product. You see that it's a product when you have a price tag.
You can see there are two categories that have the same parent (cat_drinks).
The product prod_capp is also assigned to the category and shown side by side to the categories.
I get the data currently this way:
catsOrProd.js
import { catsColl, productsColl } from '../firebase'
const state = {
catOrProducts: [],
}
const mutations = {
setCats(state, val) {
state.catOrProducts = val
}
}
const actions = {
// https://vuefire.vuejs.org/api/vuexfire.html#firestoreaction
async bindCatsWithProducts({ commit, dispatch }, CatID) {
if (CatID) {
// console.log('if CatID: ', CatID)
await Promise.all([
catsColl.where('parentCat', '==', CatID).orderBy('name', 'asc').get(),
productsColl.where('cats', 'array-contains', CatID).orderBy('name', 'asc').get()
])
.then(snap => dispatch('moveCatToArray', snap))
} else {
// console.log('else CatID: ', CatID)
await Promise.all([
catsColl.where('parentCat', '==', 'nC').orderBy('name', 'asc').get(),
productsColl.where('cats', 'array-contains', 'nC').orderBy('name', 'asc').get()
])
.then(snap => dispatch('moveCatToArray', snap))
}
},
async moveCatToArray({ commit }, snap) {
const catsArray = []
// console.log(snap)
await Promise.all([
snap[0].forEach(cat => {
catsArray.push({ id: cat.id, ...cat.data() })
}),
snap[1].forEach(cat => {
catsArray.push({ id: cat.id, ...cat.data() })
})
])
.then(() => commit('setCats', catsArray))
}
}
export default {
namespaced: true,
state,
actions,
mutations,
}
This is a part of my vue file that is showing the data on screen. I have left out the unnecessary parts.
To open everything a have a route with props and clicking on the category sends the router to the next category. (this way i can move back with browser functionality).
Sale.vue
<template>
...........
<v-col
v-for="catOrProduct in catOrProducts"
:key="catOrProduct.id"
#click.prevent="leftClickProd($event, catOrProduct)"
#contextmenu.prevent="rightClickProd($event, catOrProduct)">
....ViewMagic....
</v-col>
............
</template>
<script>
.........
props: {
catIdFromUrl: {
type: String,
default: undefined
}
},
computed: {
// https://stackoverflow.com/questions/40322404/vuejs-how-can-i-use-computed-property-with-v-for
...mapState('catOrProducts', ['catOrProducts']),
},
watch: {
'$route.path'() { this.bindCatsWithProducts(this.catIdFromUrl) },
},
mounted() {
this.bindCatsWithProducts(this.catIdFromUrl)
},
methods: {
leftClickProd(event, catOrProd) {
event.preventDefault()
if (typeof (catOrProd.parentCat) === 'string') { // when parentCat exists we have a Category entry
this.$router.push({ name: 'sale', params: { catIdFromUrl: catOrProd.id } })
// this.bindCatsWithProducts(catOrProd.id)
} else {
// ToDo: Replace with buying-routine
this.$refs.ProductMenu.open(catOrProd, event.clientX, event.clientY)
}
},
}
</script>
EDIT 24.09.2020
I have changed my binding logic to
const mutations = {
setCatProd(state, val) {
state.catOrProducts = val
},
}
const actions = {
async bindCatsWithProducts({ commit, dispatch }, CatID) {
const contain = CatID || 'nC'
const arr = []
catsColl.where('parentCat', '==', contain).orderBy('name', 'asc')
.onSnapshot(snap => {
snap.forEach(cat => {
arr.push({ id: cat.id, ...cat.data() })
})
})
productsColl.where('cats', 'array-contains', contain).orderBy('name', 'asc')
.onSnapshot(snap => {
snap.forEach(prod => {
arr.push({ id: prod.id, ...prod.data() })
})
})
commit('setCatProd', arr)
},
}
This works, as the data gets updated when I change something in the backend.
But now i get an object added everytime something changes. As example i've changed the price. Now i get this:
I don't know why, because i have the key field set in Vue. The code for the rendering is:
<v-container fluid>
<v-row
align="center"
justify="center"
>
<v-col
v-for="catOrProduct in catOrProducts"
:key="catOrProduct.id"
#click.prevent="leftClickProd($event, catOrProduct)"
#contextmenu.prevent="rightClickProd($event, catOrProduct)"
>
<div>
<TileCat
v-if="typeof(catOrProduct.parentCat) == 'string'"
:src="catOrProduct.pictureURL"
:name="catOrProduct.name"
/>
<TileProduct
v-if="catOrProduct.isSold"
:name="catOrProduct.name"
... other props...
/>
</div>
</v-col>
</v-row>
</v-container>
Why is this not updating correctly?
From the Vuefire docs, this is how you would subscribe to changes with Firebase only:
// get Firestore database instance
import firebase from 'firebase/app'
import 'firebase/firestore'
const db = firebase.initializeApp({ projectId: 'MY PROJECT ID' }).firestore()
new Vue({
// setup the reactive todos property
data: () => ({ todos: [] }),
created() {
// unsubscribe can be called to stop listening for changes
const unsubscribe = db.collection('todos').onSnapshot(ref => {
ref.docChanges().forEach(change => {
const { newIndex, oldIndex, doc, type } = change
if (type === 'added') {
this.todos.splice(newIndex, 0, doc.data())
// if we want to handle references we would do it here
} else if (type === 'modified') {
// remove the old one first
this.todos.splice(oldIndex, 1)
// if we want to handle references we would have to unsubscribe
// from old references' listeners and subscribe to the new ones
this.todos.splice(newIndex, 0, doc.data())
} else if (type === 'removed') {
this.todos.splice(oldIndex, 1)
// if we want to handle references we need to unsubscribe
// from old references
}
})
}, onErrorHandler)
},
})
I would generally avoid any unnecessary dependencies, but according to your objectives, you can use Vuefire to add another layer of abstraction, or as you said, doing some "magic binding".
import firebase from 'firebase/app'
import 'firebase/firestore'
const db = firebase.initializeApp({ projectId: 'MY PROJECT ID' }).firestore()
new Vue({
// setup the reactive todos property
data: () => ({ todos: [] }),
firestore: {
todos: db.collection('todos'),
},
})
I am doing a task where I need to wire up a search field to a simple JS application that displays a few items and the user can search through and filter them.
There are three classes - App, ProductsPanel and Search. Both Search and ProductsPanel are being initialised inside the App class.
The ProductsPanel class holds an array with 10 products.
I want to call a method of ProductsPanel from inside Search that filters through the products. How can I do that?
I've tried using this.productsPanel = new productsPanel() inside the constructor of the first class, but that brings up a new instance which doesn't have the array of all of the products.
Here's the App class:
class App {
constructor() {
this.modules = {
search: {
type: Search,
instance: null
},
filter: {
type: Filter,
instance: null
},
productsPanel: {
type: ProductsPanel,
instance: null
},
shoppingCart: {
type: ShoppingCart,
instance: null
}
};
}
init() {
const placeholders = document.querySelectorAll("#root [data-module]");
for (let i = 0; i < placeholders.length; i++) {
const root = placeholders[i];
const id = root.dataset.module;
const module = this.modules[id];
if (module.instance) {
throw new Error(`module ${id} has already been started`);
}
module.instance = new module.type(root);
module.instance.init();
// console.info(`${id} is running...`);
}
}
}
app = new App();
app.init();
And here are the Search:
export default class Search {
constructor(root) {
this.input = root.querySelector("#search-input");
}
// addEventListener is an anonymous function that encapsulates code that sends paramaters to handleSearch() which actually handles the event
init() {
this.input.addEventListener("input", () => {
this.handleSearch();
});
}
handleSearch() {
const query = this.input.value;
app.modules.productsPanel.instance.performSearch(query);
}
}
And ProductsPanel classes:
export default class ProductsPanel {
constructor(root) {
this.view = new ProductsPanelView(root, this);
this.products = [];
}
init() {
this.products = new ProductsService().products;
this.products.forEach(x => this.view.addProduct(x));
}
performSearch(query) {
query = query.toLowerCase();
this.products.forEach(p => {
if (query === p.name) {
this.view.showProduct(p.id);
} else {
this.view.hideProduct(p.id);
}
});
}
addToCart(id) {
const product = this.products.filter(p => p.id === id)[0];
if (product) {
app.modules.shoppingCart.instance.addProduct(product);
}
}
}
I want to call ProductsPanel's performSearch method but on the instance created by the App class. I have no clue on how I can do that.
Try below custom event handler class
class CustomEventEmitter {
constructor() {
this.eventsObj = {};
}
emit(eName, data) {
const event = this.eventsObj[eName];
if( event ) {
event.forEach(fn => {
fn.call(null, data);
});
}
}
subscribe(eName, fn) {
if(!this.eventsObj[eName]) {
this.eventsObj[eName] = [];
}
this.eventsObj[eName].push(fn);
return () => {
this.eventsObj[eName] = this.events[eName].filter(eventFn => fn !== eventFn);
}
}
}
How to use?
create the object of CustomEventEmitter class
let eventEmitter = new CustomEventEmitter()
Subscribe an event
emitter.subscribe('event: do-action', data => {
console.log(data.message);
});
call the event
emitter.emit('event: do-action',{message: 'My Custom Event handling'});
Hope this helps!
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?
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>