How can I query a fire store sub-collection with angular? - javascript

I am trying to query a Firestore sub collection in an ionic 4 app/angular app. My database looks as follows people(id) ----> tasks(id)
{ description : this is a description about a cat,<br>
title: this is a cat <br>
category: cat }
I am using a Firestore function to query all of the data in the collection. This is what the Firestore function looks like:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin'
admin.initializeApp()
export const getFeed = functions.https.onCall(async (req,res) =>{
const docs = await
admin.firestore().collection('people').limit(5).get()
return docs.docs.map(doc => {
return {
postID: doc.id,
...doc.data()
}
})
})
the typescript home.ts file looks like this :
const getFeed = this.aff.httpsCallable('getFeed')
this.posts = getFeed({}).subscribe(data=> {
console.log(data)
// this.posts = data
})
}
I've tried to use the array-contains option to query, but it doesn't
work. The array shows up empty on the console.
export const getFeed = functions.https.onCall(async (req,res) =>{
const docs = await
admin.firestore().collection('people').where("category",
"array-
contains", "cat").limit(5).get()
return docs.docs.map(doc => {
return {
postID: doc.id,
...doc.data()
}
})
})

It's not very clear from your question, but it looks like the category field of your database isn't actually a list type field. array-contains only works if the value is a list, not if it's just a single string value. If it's just a string, then use a == filter on it.

I have found workaround by adopting array field type instead of subcollection.
Here is an example of my code:
var coll = this.firestore.collection('credentials', ref => ref.where('tags', 'array-contains-any', ['agile']).orderBy('shortName')); // selects part of collection where roomId = roomStripped, sorted by name
return coll.snapshotChanges();
I have main collection called credentials, and a list of tags in each document.

Related

How to get data from 2 collection in firebase at a time?(similar to aggregate lookup in MongoDB) [duplicate]

I have a Cloud Firestore DB with the following structure:
users
[uid]
name: "Test User"
posts
[id]
content: "Just some test post."
timestamp: (Dec. 22, 2017)
uid: [uid]
There is more data present in the actual DB, the above just illustrates the collection/document/field structure.
I have a view in my web app where I'm displaying posts and would like to display the name of the user who posted. I'm using the below query to fetch the posts:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
const postDocs = docSnaps.docs;
for (let i in postDocs) {
loadedPosts[postDocs[i].id] = postDocs[i].data();
}
});
// Render loadedPosts later
What I want to do is query the user object by the uid stored in the post's uid field, and add the user's name field into the corresponding loadedPosts object. If I was only loading one post at a time this would be no problem, just wait for the query to come back with an object and in the .then() function make another query to the user document, and so on.
However because I'm getting multiple post documents at once, I'm having a hard time figuring out how to map the correct user to the correct post after calling .get() on each post's user/[uid] document due to the asynchronous way they return.
Can anyone think of an elegant solution to this issue?
It seems fairly simple to me:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
db.collection('users').child(doc.data().uid).get().then((userDoc) => {
loadedPosts[doc.id].userName = userDoc.data().name;
});
})
});
If you want to prevent loading a user multiple times, you can cache the user data client side. In that case I'd recommend factoring the user-loading code into a helper function. But it'll be a variation of the above.
I would do 1 user doc call and the needed posts call.
let users = {} ;
let loadedPosts = {};
db.collection('users').get().then((results) => {
results.forEach((doc) => {
users[doc.id] = doc.data();
});
posts = db.collection('posts').orderBy('timestamp', 'desc').limit(3);
posts.get().then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
loadedPosts[doc.id].userName = users[doc.data().uid].name;
});
});
After trying multiple solution I get it done with RXJS combineLatest, take operator. Using map function we can combine result.
Might not be an optimum solution but here its solve your problem.
combineLatest(
this.firestore.collection('Collection1').snapshotChanges(),
this.firestore.collection('Collection2').snapshotChanges(),
//In collection 2 we have document with reference id of collection 1
)
.pipe(
take(1),
).subscribe(
([dataFromCollection1, dataFromCollection2]) => {
this.dataofCollection1 = dataFromCollection1.map((data) => {
return {
id: data.payload.doc.id,
...data.payload.doc.data() as {},
}
as IdataFromCollection1;
});
this.dataofCollection2 = dataFromCollection2.map((data2) => {
return {
id: data2.payload.doc.id,
...data2.payload.doc.data() as {},
}
as IdataFromCollection2;
});
console.log(this.dataofCollection2, 'all feeess');
const mergeDataFromCollection =
this.dataofCollection1.map(itm => ({
payment: [this.dataofCollection2.find((item) => (item.RefId === itm.id))],
...itm
}))
console.log(mergeDataFromCollection, 'all data');
},
my solution as below.
Concept: You know user id you want to get information, in your posts list, you can request user document and save it as promise in your post item. after promise resolve then you get user information.
Note: i do not test below code, but it is simplify version of my code.
let posts: Observable<{}[]>; // you can display in HTML directly with | async tag
this.posts = this.listenPosts()
.map( posts => {
posts.forEach( post => {
post.promise = this.getUserDoc( post.uid )
.then( (doc: DocumentSnapshot) => {
post.userName = doc.data().name;
});
}); // end forEach
return posts;
});
// normally, i keep in provider
listenPosts(): Observable<any> {
let fsPath = 'posts';
return this.afDb.collection( fsPath ).valueChanges();
}
// to get the document according the user uid
getUserDoc( uid: string ): Promise<any> {
let fsPath = 'users/' + uid;
return this.afDb.doc( fsPath ).ref.get();
}
Note: afDb: AngularFirestore it is initialize in constructor (by angularFire lib)
If you want to join observables instead of promises, use combineLatest. Here is an example joining a user document to a post document:
getPosts(): Observable<Post[]> {
let data: any;
return this.afs.collection<Post>('posts').valueChanges().pipe(
switchMap((r: any[]) => {
data = r;
const docs = r.map(
(d: any) => this.afs.doc<any>(`users/${d.user}`).valueChanges()
);
return combineLatest(docs).pipe(
map((arr: any) => arr.reduce((acc: any, cur: any) => [acc].concat(cur)))
);
}),
map((d: any) => {
let i = 0;
return d.map(
(doc: any) => {
const t = { ...data[i], user: doc };
++i;
return t;
}
);
})
);
}
This example joins each document in a collection, but you could simplify this if you wanted to just join one single document to another.
This assumes your post document has a user variable with the userId of the document.
J

React Firestore get id when creating document [duplicate]

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'});
}

CRUD in React with Firestore for data in an array

It's my first time using Firebase as my backend and I can successfully access and fetch the data in my React App, but I have problem with CRUD operations.
Example of my collection:
- closets (collection)
- unique ID (specific user)
- name (string)
- shorts (array of objects - map)
- 0 (first object in array)
- name (string)
- image (string)
.........
.........
I can read all the files, but I can't update or delete them. I need to access all data in arrays (e.g shorts array based on example above) and be able to change name or remove the whole object.
Using custom hook to map the data:
export const useGetData = () => {
const [documents, setDocuments] = React.useState([]);
const db = firebase.firestore();
React.useEffect(() => {
db.collection("closets")
.get()
.then((querySnapshot) => {
let arr = [];
querySnapshot.docs.map((doc) =>
arr.push({ id: doc.id, name: doc.data() })
);
setDocuments(arr);
});
}, [db]);
return [documents];
};
Here I am trying to update name in dress array
const [nameValue, setNameValue] = React.useState("");
const db = firebase.firestore();
const getNameValue = (event) => {
setNameValue(event.target.value);
};
const updateValue = () => {
db.collection("closets")
.doc(doc)
.update({
"dress.name": nameValue
})
.then(function () {
console.log("Document successfully updated!");
})
.catch(function (error) {
console.error("Error updating document: ", error);
});
};
return (
<>
<input onBlur={getNameValue} type="text" />
<button onClick={updateValue}>Update</button>
</>
Getting error s.indexOf is not a function
I am not really sure how achieve updating this specific string in object, which is part of array.
Here is created example in codesandbox, for better understanding:
https://codesandbox.io/s/second-leeway-test-forked-ii0p6
You can update the array using:
firebase.firestore.FieldValue.arrayUnion(elem)
firebase.firestore.FieldValue.arrayRemove(elem)
See: https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array
To update an element you must first remove it from the array and then add the modified one.
However, I suggest you model the database through subcollections, so as to simplify their management, like this:
- closets (collection)
- unique ID (doc)
- name (string)
- shorts (collection)
- short (doc)
- name (string)
- image (string)
See: https://firebase.google.com/docs/firestore/data-model#subcollections

Returning UID in Firebase query

The following query below returns me a collection of data based on a user id. How do I do I make sure it returns the uid of each document in the query.
getWebsitesByUserId(id) {
return this.afs.collection('websites', ref => ref.where('createdBy', '==', id)).valueChanges();
}
I understand that it involves something like this:
return this.afs.collection('websites').doc(id).snapshotChanges().pipe(map(action => {
const data = action.payload.data();
const uid = action.payload.id;
return {uid, ...data};
}));
Just not sure how to implement in a query.
According to this pull request on the AngularFire Repo now there is an option that you can pass a string to the valueChanges method on a collection reference.
This string will be the name of the property that will hold the IDs of the documents.
For example:
collectionRef.valueChanges('myIdKey').subscribe()
Would emit:
emits [ { myIdKey: 'MrfFpRBfWLTd7LqiTt9u', ...data }, ... ]
So in your situation I guess it would be something like this:
getWebsitesByUserId(id) {
return this.afs.collection('websites', ref => ref.where('createdBy', '==', id)).valueChanges('uid');
}

How to get the unique id (key) of a document in Firebase database collection?

I am trying to fetch the documents of a collection in my React-Native app but I dont know how to fetch them by ID (key).
P.S: I dont have any field called unique id or id inside the document but as I have understood, the unique id is the auto-generated key when I create a document which stands as the name of that document (20 characters id).
This is how I fetch all the fields inside a document:
var ref = firebase.firestore().collection('discounts')
.orderBy('rest_id')
EDIT:
getDiscounts = () => {
try {
this.setState({
loading: true
})
var ref = firebase.firestore().collection('discounts')
.orderBy('rest_id')
.limit(this.state.limit)
ref.onSnapshot((querySnapshot => {
var discounts = querySnapshot.docs.map(document => document.data());
var lastVisibleDiscount = discounts[discounts.length - 1].rest_id;
this.setState({
discounts: discounts,
lastVisibleDiscount: lastVisibleDiscount,
loading: false,
});
}));
}
catch (error) {
console.log(error);
}
}
To print the keys of the documents in the collection in the order of the value of their rest_id field, you can do something like this:
firebase.firestore().collection("discounts").orderBy('rest_id').get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id);
});
});
This is an almost literal copy of the code in the documentation on getting all documents from a collection, so I recommend spending some time there.
You can use (using await/async)
const ref = await ref.get()
It will have an array called docs that you can map over to get the id and data of the document:
const data = ref.docs.map(doc => {return {id: doc.id, data: doc.data()} })

Categories

Resources