hope you are coding well, I am using firebase and vue js to build a messenger application.
Each user has a document, and within each document there is a field named includeInMsg, which contains the Id of the message that the user included. I know how to read one document by its id, but I don't know how to read multiple documents by their ids.
here you can see my code:
import { ref } from "vue";
import { db } from "src/boot/firebase";
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
import {
addDoc,
collection,
query,
where,
getDocs,
getDoc
} from "firebase/firestore";
import { doc, onSnapshot } from "firebase/firestore";
export default {
setup() {
return {
userList: ref([]),
userMessageRefList: ref([]),
userMessageDocList: ref([]),
freelancerProfile: ref([]),
messages: ref([]),
userMessage: ref([]),
listofdata: ref([])
};
},
async created() {
// check if user loged in or not
const auth = getAuth();
onAuthStateChanged(auth, user => {
if (user) {
// store current user uid in userlist
this.userList.unshift(user.uid);
this.renderMessageList();
} else {
this.$router.push("/");
}
});
},
methods: {
// render user message list doc id
async renderMessageList() {
const currentUserProfile = query(
collection(db, "userProfile"),
where("uuid", "==", this.userList[0])
);
const userProfileQuerySnapshot = await getDocs(currentUserProfile);
userProfileQuerySnapshot.forEach(doc => {
let docData = doc.data();
this.userMessageRefList.unshift(docData.messageList);
this.listofdata.unshift(docData);
});
this.userMessageRefList.forEach(elem =>
// store doc id into userMessageDocList
elem.forEach(le => this.userMessageDocList.unshift(le))
);
console.log(this.userMessageDocList) //output: {0: {…}, 1: {…}}
// now I need to read each document by their doc Id and store it in an array to render
},
},
computed: {
checkMessages: function() {
if (this.messages.length > 1) {
return true;
} else {
return false;
}
}
}
};
I tried to use for loop to read each document and store the value in an array but I got SyntaxError: Unexpected reserved word 'await'. (71:26) error.
As confirmed in the comments, this.userMessageDocList contains a compounded object containing objects with the documents IDs.
In this case you should use Promise.all() to trigger a variable number of calls to Firestore in parallel.
As explained in the doc (see link above):
The Promise.all() method takes an iterable of promises as an
input, and returns a single Promise that resolves to an array of the
results of the input promises.
So you need to loop over the object, grab the Docs IDs and populate an Array of Promises that you pass to Promise.all(). In pseudo code (I let you use the method you want to loop over the object, there are a lot of examples on SO) it's something along the following lines:
const promisesArray = [];
<Loop For each element>
// Assumption: element.docId contains the Firestore doc ID
promisesArray.push(getDoc(doc(db, "yourCollection", element.docId)))
</End Loop>
const docSnapshotsArray = await Promise.all(promisesArray);
docSnapshotsArray.forEach(docSnap => {
console.log(docSnap.data());
// ...
});
docSnapshotsArrays is an Array of DocumentSnapshots. You can then loop over this array and do whatever you want.
I don't now if this is important for your specific case, but note that with Promise.all(), the returned values in the resulting Array will be in order of the Promises passed, regardless of completion order.
Related
I'm trying to sync my app to a firebase database to retrieve some data. With the code below, I'm able to console log 'polls' which is a basic collection I made in Firebase, and I can see all my data in the console log.
But if I actually try to reach into the polls object using polls.data.id or polls.data.label or even just polls.data, the page blanks out and I get an error saying that it "Cannot read the properties" that I'm calling for. Any help would be greatly appreciated!
import { useEffect, useState } from "react"
import { collection, getDocs } from "firebase/firestore"
import { db } from '../firebase.config'
function Food() {
const [polls, setPolls] = useState([])
useEffect(() => {
getPolls()
}, [])
useEffect(() => {
console.log(polls.data.id);
}, [polls])
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const poll = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
setPolls(poll)
}).catch(error => console.log(error.message))
}
return (
<div>
Food
</div>
)
}
export default Food
Since my app is clearly fetching the object properly, I'm not sure why I'm not able to actually retrieve the data from within the object to use in my app.
Your polls is an array, so you can't just console.log(polls.data.id). Since polls is an array, it has no data attribute, and thus you can't call id on that, which is what the error says.
Instead, loop over the results with:
useEffect(() => {
polls.forEach((pol) =>
console.log(poll.id);
})
}, [polls])
Note that I also console.log(poll.id), since you map the ID to the object itself, not in its data property.
Heres what i am trying to achieve i want a unique id field for every document in my database and i want that unique id to the same as the document id.
Example:
documents: data:
eBgdTRE123 id:'eBgdTRE123'
name:'Jhon Doe'
job:'Programmer'
i want i databse to have this structure, now i got two ideas to achieve this
1: to use cloud function and have onCreate listener and everytime theres a new document grab document and set the id field and update it heres how i am doing it
exports.addDocIdToField =
functions.firestore.document('collectionname/{docID}').onCreate((snap,context) => {
const id = context.params.docID;
return admin.firestore().collection('collectionname')
.doc(id).set({ id: snap.id }, { merge: true })
.then(() => {
return null;
})
.catch((error) => {
return null;
});
})
2: to use the above kind of method on document creation. add a new document as soon as that document is added get the newly added document and update its id
both of them work but my question is can i rely on this kind of operation? i mean if the id is by any means undefined it can cause an error further in the app.
or if there are other ways to achieve this?
See JS SDK v9 syntax at the bottom
There is a simpler way to achieve that, using the doc() method, as follows (here with the JavaScript SDK v8)
var newDocRef = db.collection('collectionname').doc();
newDocRef.set({
name:'Jhon Doe',
job:'Programmer',
id: newDocRef.id
})
As explained in the doc:
(the doc() method) gets a DocumentReference for the document within the collection at the
specified path. If no path is specified, an automatically-generated
unique ID will be used for the returned DocumentReference.
You will find similar methods in the other Client SDKs, here for Android and here for iOS.
UPDATE FOR JS SDK v9:
import { collection, doc, setDoc } from "firebase/firestore";
const newDocRef = doc(collection(db, "collectionname"));
await setDoc(
newDocRef,
{
name:'Jhon Doe',
job:'Programmer',
id: newDocRef.id
}
)
the previous method works fine but just for the sake of clarification
what is it really like
const { doc, collection, getFirestore, setDoc, addDoc } = require('firebase/firestore');
let collectionId = "Cars";
let docId;
let firestore = getFirestore();
async function addDocWithId() {
let collectionRef = collection(firestore, collectionId)
addDoc(collectionRef, {}).then(res => {
docId = res.id
let docRef = doc(firestore, collectionId + "/" + docId)
setDoc(docRef, {
id: docId,
car: 'Benz'
})
})
};
how it has been clarified
const { doc, collection, getFirestore, setDoc, addDoc } = require('firebase/firestore')
let collectionId = "Cars"
let firestore = getFirestore()
async function addDocWithId() {
let collectionRef = collection(firestore, collectionId)
let docRef = doc(collectionRef)
setDoc(docRef, {
id: docRef.id,
car: "Volvo"
})
}
If in case if there's anyone who didn't have luck with above provided answers, try this -> docref.set({ 'id':docref.ref.id}). It worked for me. Below is a usecase of this.
create(tutorial: any): any {
var docref = this.db.collection('invxxx').doc()
docref.set({ 'id':docref.ref.id, anotherField: 'anotherValue'});
}
I am using this request to get my documents from Firestore asynchronously.
During an operation (delete for example) the list of my documents is not updated automatically. How can I transform my asynchronous function into an observable in order to take advantage of the real-time functionalities of Firestore and get the id of the document ?
import { Injectable } from '#angular/core';
import { SentencePair } from '../models/sentence-pair.model';
import { Firestore, collectionData, deleteDoc,limit, limitToLast,orderBy, increment,
addDoc, collection, doc, updateDoc, setDoc,query, where, getDocs } from
'#angular/fire/firestore';
import { Observable,combineLatest,map, defer } from 'rxjs';
#Injectable({ providedIn: 'root'})
export class SentencesPairsService {
constructor(private firestore: Firestore) { }
async FilterPairs(field: string, boolean: boolean, display:any) {
const sentencepairsRef = collection(this.firestore, 'SentencesPairs');
const q = query(sentencepairsRef,
where('validation', '==', boolean),
where('origin_trad', 'in', display),
orderBy(field),limitToLast(10));
const truc = await getDocs(q)
return truc.docs.map(docData=>({...docData.data(), id:docData.id}));
}
I use AngularFire 7.2. Thanks for your help.
Get an observable for data from Firestore query
If you want an observable from Firestore, you need to return the .valueChanges() on a AngularFirestoreCollection. Read this doc for reference: https://github.com/angular/angularfire2/blob/master/docs/firestore/querying-collections.md.
For example :
getUserByEmail(email: string): Observable<User> {
const collection = this.firestore.collection<User>('users', ref => ref.where('email', '==', email))
const user$ = collection
.valueChanges()
.pipe( map(users => {
const user = users[0];
console.log(user);
return user;
}));
return user$;
}
Get ID of the document
If you want the ID( which is not returned in valueChanges())on your document you need to use snapshotChanges(). Sometimes it's easier to maintain the id on the user data when saving to Firestore to avoid using snapshotChanges. Also SwitchMap is helpful in this case as when a new value comes from the source (userId in this case) it will cancel its previous firestore subscription and switch into a new one with the new user id. If for some reason you want to maintain firestore subscriptions for all userIds that come through at once, then use mergeMap instead. But typically you only want to be subscribed to one user's data at a time.
// Query the users by a specific email and return the first User with ID added using snapshotChanges()
return this.firestore.collection<User>('users', ref => ref.where('email', '==', email))
.snapshotChanges()
.pipe(map(users => {
const user = users[0];
if (user) {
const data = user.payload.doc.data() as User;
const id = user.payload.doc.id;
return { id, ...data };
}
else { return null;
}
}));
Note to subscribe to the changes :
The user will be null until you call subscribe on this.user$ :
const queryObservable = size$.pipe(
switchMap(size =>
afs.collection('items', ref => ref.where('size', '==', size)).snapshotChanges()
)
);
queryObservable.subscribe(queriedItems => {
console.log(queriedItems);
});
OR
In your html use the async pipe which handles subscribe and unsubscribe on an observable like below:
<div *ngIf="(user$ | async) as user">{{ user.email }}</div>
Try to avoid using the async pipe on the same observable multiple times in your html, and instead set it as a local html variable which i do above (as user) to avoid unnecessary data trips to the db.
I'm trying to create a function to add documents to firestore. At the moment this function could be called two different ways. 1) Adding a user to a collection called 'Users'. 2) adding a site to a collection called 'Sites'.
This function should take the following parameters:
The name of the collection (required)
The users uid (make this optional)
An object with the data for a site (make this optional)
I'm also trying to utilize JS modules to keep my code better organised. At the moment my folder looks something like this:
myApp
assets
img
dist
index.html
index.js
modules
auth.js
firestore.js
Inside auth.js I have a signUp() inside witch I want to call a function called addToFirestore() (this comes from firestore.js). My code looks something like this:
firestore.js
import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.6.4/firebase-
app.js';
import { getFirestore, setDoc, doc } from
'https://www.gstatic.com/firebasejs/9.6.4/firebase-firestore.js';
const firebaseConfig = {
...
};
// Initialize Firebase
initializeApp(firebaseConfig);
const db = getFirestore;
function addToFirestore(collName, user = 0, data = 0) {
// check if adding user
if (user != 0 && data == 0){
//adding user to firestore
try {
setDoc(doc(db, collName, user.uid), {
email: user.email,
});
} catch (e) {
console.error('Error adding document: ', e);
}
// check if adding site
} else if (data != 0 && user == 0) {
setDoc(doc(db, collName), data);
}
export { addToFirestore};
Inside auth.js calling function like this:
// * Auth
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword } from
'https://www.gstatic.com/firebasejs/9.6.4/firebase-auth.js';
import {addToFirestore} from '/modules/firestore.js';
function signUp(email, password) {
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
addToFirestore('Users', user);
})
.then(() => {
openApp();
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
alertAuth.classList.remove('d-none');
alertAuth.classList.add('show');
alertAuth.innerHTML = `<strong>Error: </strong> ${errorCode}`;
});
}
And inside index.js calling signUp():
btnAuthSignUp.addEventListener('click', function () {
event.preventDefault();
let email = inpAuthEmail.value;
let password = inpAuthPassword.value;
signUp(email, password);
});
And it is giving me an error like this:
firestore.js:31 Error adding document: FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
I tried doing everything in one file and still got the same problem. Any help would be greatly appreciated.
As stated on the error you encountered:
Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore.
Firestore expects a collection reference to pass the data to. You didn't specify your collection reference. You should put a collection reference by using given code below:
const collectionRef = doc(db, 'collection_name', user.uid);
db here is not a collection reference. Its just an instance of Firestore:
const db = getFirestore;
Then use it as code below:
setDoc(collectionRef, {
email: user.email,
});
You could also check Add data to Cloud Firestore for more information.
Heres what i am trying to achieve i want a unique id field for every document in my database and i want that unique id to the same as the document id.
Example:
documents: data:
eBgdTRE123 id:'eBgdTRE123'
name:'Jhon Doe'
job:'Programmer'
i want i databse to have this structure, now i got two ideas to achieve this
1: to use cloud function and have onCreate listener and everytime theres a new document grab document and set the id field and update it heres how i am doing it
exports.addDocIdToField =
functions.firestore.document('collectionname/{docID}').onCreate((snap,context) => {
const id = context.params.docID;
return admin.firestore().collection('collectionname')
.doc(id).set({ id: snap.id }, { merge: true })
.then(() => {
return null;
})
.catch((error) => {
return null;
});
})
2: to use the above kind of method on document creation. add a new document as soon as that document is added get the newly added document and update its id
both of them work but my question is can i rely on this kind of operation? i mean if the id is by any means undefined it can cause an error further in the app.
or if there are other ways to achieve this?
See JS SDK v9 syntax at the bottom
There is a simpler way to achieve that, using the doc() method, as follows (here with the JavaScript SDK v8)
var newDocRef = db.collection('collectionname').doc();
newDocRef.set({
name:'Jhon Doe',
job:'Programmer',
id: newDocRef.id
})
As explained in the doc:
(the doc() method) gets a DocumentReference for the document within the collection at the
specified path. If no path is specified, an automatically-generated
unique ID will be used for the returned DocumentReference.
You will find similar methods in the other Client SDKs, here for Android and here for iOS.
UPDATE FOR JS SDK v9:
import { collection, doc, setDoc } from "firebase/firestore";
const newDocRef = doc(collection(db, "collectionname"));
await setDoc(
newDocRef,
{
name:'Jhon Doe',
job:'Programmer',
id: newDocRef.id
}
)
the previous method works fine but just for the sake of clarification
what is it really like
const { doc, collection, getFirestore, setDoc, addDoc } = require('firebase/firestore');
let collectionId = "Cars";
let docId;
let firestore = getFirestore();
async function addDocWithId() {
let collectionRef = collection(firestore, collectionId)
addDoc(collectionRef, {}).then(res => {
docId = res.id
let docRef = doc(firestore, collectionId + "/" + docId)
setDoc(docRef, {
id: docId,
car: 'Benz'
})
})
};
how it has been clarified
const { doc, collection, getFirestore, setDoc, addDoc } = require('firebase/firestore')
let collectionId = "Cars"
let firestore = getFirestore()
async function addDocWithId() {
let collectionRef = collection(firestore, collectionId)
let docRef = doc(collectionRef)
setDoc(docRef, {
id: docRef.id,
car: "Volvo"
})
}
If in case if there's anyone who didn't have luck with above provided answers, try this -> docref.set({ 'id':docref.ref.id}). It worked for me. Below is a usecase of this.
create(tutorial: any): any {
var docref = this.db.collection('invxxx').doc()
docref.set({ 'id':docref.ref.id, anotherField: 'anotherValue'});
}