When I get several documents from a collection, the result is only an array with each doc data.
firestore.collection("categories").valueChanges().subscribe(data => {
console.log(data);
// result will be: [{…}, {…}, {…}]
};
How can I get the name of each doc?
The ideal result would look like this:
{"docname1": {…}, "docname2": {…}, "docname3": {…}}
// Print each document
db.collection("categories")
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.data()); // For data inside doc
console.log(doc.id); // For doc name
}
}
When you need to access additional metadata like the key of your Document, you can use the snapshotChanges() streaming method.
firestore.collection("categories").valueChanges().map(document => {
return document(a => {
const data = a.payload.doc.data();//Here is your content
const id = a.payload.doc.id;//Here is the key of your document
return { id, ...data };
});
You can review the documentation for further explanation and example
This is dart code:
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('your collection')
.snapshots(), // path to collection of documents that is listened to as a stream
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
return ListView(
children: snapshot.data.docs.map((DocumentSnapshot doc) { // get document data
return yourWidget( // return a widget for every document in the collection
docId: doc.id // get document name
);
}).toList(), // casts to list for passing to children parameter
);
},
),
Related
How can I get the same elements what the user asked?
for example if I do this, the user asked 30
query = query.limit(30)
const qSnap = await query.get(); // 30 objects
qSnap.docs.forEach((doc) => { // i will get fewer than 30
const item = doc.data().data.tools.find(t => t.trait === 'color1' && t.value == 'red');
console.log(item)
})
i need to filter because i have this structure:
{
name:carla
data: "{
sign: "carly",
tools: [{trait:color1, value:red}, {trait:color2, value:white}] }"
},{
name:dany
data: "{
sign: "dan",
tools: "[{trait:color1, value:blue}, {trait:color2, value:black}]
}"
}
or how can i enhacement my structure to dont have this problem?
Taking Stewart's answer and changing it a bit (I couldn't do that in a comment, sorry)
const toolsFilter = {
trait: 'color1',
value:'red'
}
const qSnap = await query.where('tools','array-contains-any', toolsFilter)
.limit(30)
.get();
qSnap.docs.forEach((doc) => {
const item = doc.data().data;
console.log(item)
}))
The array-contains operations checks if an array, contains a specific (complete) value. It can't check if an array of objects, contains an item with a specific value for a property. The only way is to query the entire object inside the array.
In this example structure:
data: {
sign: "carly",
tools: [{trait:color1, value:red}, {trait:color2, value:white}] }
}
You want to query objects inside a map of an array. See Firestore screenshot below for better visualization:
To be able to query objects inside a map of an array, you must query the whole object inside of it. See example query below:
// As you can see here, you need to be able to jump inside the `data.tools`
// Then query the `tools` array by using objects.
const toolsRef = db.collection("someCollection").where("data.tools", "array-contains", {trait: "color1", value: "red"})
Here's a complete code for reference:
const toolsRef = db.collection("someCollection").where("data.tools", "array-contains", {trait: "color1", value: "red"})
query = toolsRef.limit(30)
query.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// Do anything with the result.
console.log(doc.id, " => ", doc.data());
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
The result should be something like this:
5nwzwpxr7BmctvznEypl => {
name: 'carla',
data: { tools: [ [Object], [Object] ], sign: 'carly' }
}
For more information, See Array Memberships.
UPDATE:
Firestore does not have a way to search from the document's fields which have a JSON object encoded inside. You should parse the JSON object first to get/filter the necessary data. e.g.:
query = query.limit(30);
const qSnap = await query.get();
qSnap.docs.forEach((doc) => {
const data = JSON.parse(doc.data().data);
const items = data.tools.filter(t => t.trait === "color1" && t.value === "red");
console.log(items);
})
However, the above snippet that is similar to yours could lead into a problem which is not the same as the limit() you set on your query. To enhance your structure, I would suggest to put it in document fields like I gave on my original answer above.
document
(fields)
|- name(string): "carla"
|- data(map)
|- sign(string): "carly"
|- tools(array)
|- (map)
| - trait(string): "color1"
| - value(string): "red"
|- (map)
- trait(string): "color2"
- value(string): "white"
This structure is the same as your JSON object encoded inside the data field. The advantage of using this structure is you can now query using Firestore which I showed to you on my original post. This would result in 30 documents without using a client side filtering. It will be only fewer than 30 if the query can't find matched documents.
To do this, you just need to construct your JSON object and set the data to the document. See e.g. below:
db.collection("someCollection").add({
data: {
sign: "carly",
tools: [{trait: "color1", value:"red"}, {trait:"color2", value:"white"}]
},
name: "carla"
})
.then((docRef) => {
console.log("Document written with ID: ", docRef.id);
})
.catch((error) => {
console.error("Error adding document: ", error);
});
You need to filter the data first, them limit it. Using this synatax.
const toolsFilter = {
trait: 'color1',
value:'red'
}
const qSnap = await query.where('tools','array-contains',toolsFilter)
.limit(30)
.get();
qSnap.docs.forEach((doc) => {
const item = doc.data().data;
console.log(item)
}))
See the Firebase online documentation for more info on query syntax etc.. https://firebase.google.com/docs/firestore/query-data/queries
Also this explains pagination using query cursors https://firebase.google.com/docs/firestore/query-data/query-cursors
I am not into javascript and angular, currently working on some UI task and has workable/little knowledge of javascript
what i want, base on a key field filter out the objects.
currently what code is doing, hitting a api, which is storing some result in an array. this array result are in object (promise object) i guess, and i need to set out a filter condition on this object base on some value.
but for me issue is, object does not have key same as response, it store. data like this
Array(5) [ {…}, {…}, {…}, {…}, {…} ]
0: Object { _isScalar: false, source: {…}, operator: {…} }
1: Object { _isScalar: false, source: {…}, operator: {…} }
2: Object { _isScalar: false, source: {…}, operator: {…} }
3: Object { _isScalar: false, source: {…}, operator: {…} }
4: Object { _isScalar: false, source: {…}, operator: {…} }
length: 5
so even if i iterate through each object i cant find the key which is my filter criteria.
below is code related to this
getSomeThing(
name: string,
dcId: any
): Promise<any[]> {
const url = `<url>`;
return this.http.get(url, { headers: this.sharedService.getHeaders() })
.toPromise()
.then(response => this.getSomeThingOneByOne(name, dcId, response.json()))
.catch(error => this.sharedService.handleError(error));
}
private getSomeThingOneByOne(name, dcId, someIds): Promise<any> {
const someObservables = [];
someIds.forEach(some => someObservables.push(this.getsomethingObservable(name, dcid, some)));
return Observable.forkJoin(someObservables)
.toPromise()
.catch(error => this.sharedService.handleError(error));
}
getsomethingObservable(
name: string,
dcId: any,
someId: any
): Observable<any> {
const url = `<url>`;
return this.http.get(url, { headers: this.sharedService.getHeaders() })
.map((response: Response) => {
const vm = response.json();
return vm;
})
.catch((error: any) => Observable.throw(this.sharedService.handleError(error)));
}
note i have change the name of function and variable.,
now here, getsomeThing call an api which return a list of ids and pass it to getSomethingOneByOne as array ie [1,2,3,4,5] , and this function getSomethingOneByOne call getsomethingObservable to get the data related to that id.
what i want is once i recied the data i should check a certain value in it or in getsomethingObservable check the value it self for that key and filter out the result.
but due since i am not able to read the data in array someObservables in function getSomeThingOneByOne raw i cant add filter condition there.
need help here as it requires knowledge of promise, obserable, angular and typescript which i lack.
just solved this by taking reference from #sonusindhu suggestion and taking reference from here too.
i had made changes on getSomeThingOneByOne as below and it worked
private getSomeThingOneByOne(name, dcId, someIds): Promise<any> {
const someObservables = [];
someIds.forEach(some => someObservables.push(this.getsomethingObservable(name, dcid, some)));
return Observable.forkJoin(someObservables)
.toPromise()
.then(response => {
var data = response.filter(val=>val[<somekey>]!='<a certain value>');
return data;
})
.catch(error => this.sharedService.handleError(error));
}
How do I change the values of my object to use the fixed values as I have done with my console.log?
fetchData = () => {
axios.get(fullAPI).then(res => {
const apiResponse = res.data
apiResponse.forEach(employee => console.log('test', employee.value.toFixed(2)))
apiResponse.forEach(employee => employee.value.toFixed(2))
console.log('raw data', apiResponse)
this.setState({
employeeData: apiResponse,
})
})
}
test 4.41
test 5.00
test 6.16
test 0.79
raw data
(10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
0: {name: "Animals", value: 4.41361634}
1: {name: "Environment", value: 5.004498622999998}
The error with your code is that you are just formatting the value number and returning it to forEach (that does nothing with this information).
You must modify the original object.
fetchData = () => {
axios.get(fullAPI).then(res => {
const apiResponse = res.data;
apiResponse.forEach(employee => {
employee.value = employee.value.toFixed(2); // Note we are attributing the fixed value to the value itself, modifying the object
});
console.log('raw data', apiResponse)
this.setState({
employeeData: apiResponse
})
})
}
Note that this works because JavaScript always use a reference for objects (like a pointer in C), so even without returning the object, you are changing it properties and it will reflect on the original array.
IMO is more readable for non experienced (and experienced as well) programmers using Array.map() instead, since it will make clear that you are modifying the object and updating the array:
fetchData = () => {
axios.get(fullAPI).then(res => {
const apiResponse = res.data;
apiResponse.map(employee => {
employee.value = employee.value.toFixed(2)
return employee;
});
console.log('raw data', apiResponse)
this.setState({
employeeData: apiResponse
})
})
}
Both codes will do the same, probably no performance differences, just different readability.
You need to set employee.value to the desired value. Additionally, I recommend not using the implicit return syntax and use braces for readability.
apiResponse.forEach(employee => {
employee.value = employee.value.toFixed(2);
});
I have been working in the LAMP stack for years and am struggling to wrap my head around advanced querying with Firebase and NoSQL databases. I would like to return 5 random documents from a collection. Below is the VueJS code written thus far:
Here is the data object that I have created:
data () {
return {
courseIds: [],
}
}
Here is my created lifecycle hook where I'm querying the Firebase NoSQL database:
created() {
// fetch data from firestore
database.collection('courses').get()
.then(snapshot => {
snapshot.forEach(doc => {
let course = doc.data()
course.id = doc.id
this.courseIds.push(course.id)
})
})
}
Because I'm looking to randomize the data returned, I have added the beforeMount lifecycle hook which calls a Fisher-Yates shuffle method. The plan was to shuffle the returned data and then return only the first 5 documents:
beforeMount() {
this.courseIds = this.shuffle(this.courseIds)
}
And the method:
methods: {
shuffle: function(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
}
I'm a slow learner, so more than likely this is a numbskull approach to the problem. My present issue is that Firebase is returning the Ids as an object rather than an array so my shuffle method isn't working as anticipated. See the console.log() below:
[__ob__: Observer]
0: "0HnqJ8zZg1Rs3D4qod4l"
1: "1gZmoUpCOSDeLsYMDi4v"
2: "JrJj3a84qKTD72ncvGXd"
3: "LWMbY98m3sKLrHNDSUkW"
4: "SUn1kxHzMo7fu5urpNB5"
5: "kQRWQIj0mFXIWVJcaouY"
length: 6
__ob__: Observer {value: Array(6), dep: Dep, vmCount: 0}
__proto__: Array
Does anybody have a better approach to this functionality, or a good way to convert the courseIds into an array to be shuffled?
What happens if you do a bit differently, as follows:
created() {
// fetch data from firestore
database.collection('courses').get()
.then(snapshot => {
let courseIdsArray = [];
snapshot.forEach(doc => {
courseIdsArray.push(doc.id);
});
this.courseIds = courseIdsArray;
});
}
I do a http call to get an Array with objs. And now I want to call for each objs that return me an ID another http call. After all I want to have one observable result.
So far I managed to get for each index a http call. The problem instead of one result I got multiple.
getStats(tag: string) {
return this.service.getClanByClanTag(tag)
.map(clan => {
return clan.memberList; //the arr that return the ID's
})
.switchMap((member: PlayerByMemberListType[]) => {
return member; // singleObj of the arr
})
.concatMap((singleMember) => {
return this.service.getPlayerData(singleMember.tag).map(player => {
//push data to the new arr which should return only one time
this.newArr.push({
tag: singleMember.tag,
name: singleMember.name,
warStars: player.warStars,
trophiesNightBase: singleMember.versusTrophies
});
return this.newArr;
});
});
}
This is what the console prints out after subscribing to it:
Array [ {…} ]
Array [ {…}, {…} ]
Array(3) [ {…}, {…}, {…} ]
Array(4) [ {…}, {…}, {…}, {…} ]
Array(5) [ {…}, {…}, {…}, {…}, {…} ]
...
I know I need some kind of Observable.forkJoin but I don't know how integrate it in the code.
Try something like this:
this.service.getClanByClanTag(tag)
.mergeMap(clan => clan.memberList)
.mergeMap(
member => this.service.getPlayerData(member.tag), // supposedly this returns an observable
(member, player) => ({
tag: member.tag,
name: member.name,
warStars: player.warStars,
trophiesNightBase: member.versusTrophies
})
)
.toArray()
So basically what you want to achieve is this.
Get the clan info
Using clan info from step 1, get the memberList in the clan
For each member inside the memberList, get the players
You will need to think of a way to preserve the info at step2 when before switchMap in step3. Usually we will use a Subject, but in the case if you do not want to, simply map the Observable to preserve the data:
getStats(tag: string) {
return this.service.getClanByClanTag(tag)
.map(clan => {
return clan.memberList; //the arr that return the ID's
})
.switchMap((memberList: PlayerByMemberListType[]) => {
//note that the following map is a function of javascript array, not Observable
//it returns an array
let arrayOfObservables = memberList.map(singleMember => {
this.service.getPlayerData(singleMember.tag)
//map the data so as to preserve the data of singleMember
//by creating a new object, using Object.assign
.map(playerData => {
return Object.assign({memberData: singleMember}, playerData,)
});
})
return Observable.forkJoin(arrayOfObservables);
})
.map(players => {
//players is an array of Object that is the format of {memberData:singleMember, playerData:player)
//perform Object destructuring method
return players.map(({memberData,playerData}) => {
return {
tag: memberData.tag,
name: memberData.name,
warStars: playerData.warStars,
trophiesNightBase: memberData.versusTrophies
}
})
})
}