Angular/Firestore Collection Document Query to return a single document field from all documents into an array - arrays

I am performing a query on my collection documents and trying to return just all phone numbers into an array. I just want to set the phone numbers into array for use by another function. Firebase docs only show a console log for (doc.id) and (doc.data) and no practical use for any other objects in your documents. My console log for info.phoneNumbers returns all the phoneNumbers.
async getPhone() {
await this.afs.collection('members', ref => ref.where('phoneNumber', '>=', 0))
.get().toPromise()
.then(snapshot => {
if (snapshot.empty) {
console.log('No Matches');
return;
}
this.getInfo(snapshot.docs);
});
}
getInfo(data) {
data.forEach(doc => {
let info = doc.data();
console.log(info.phoneNumber, 'Phonenumbers');
// let myArray = [];
// myArray.push(doc.doc.data());
// const phoneNumber = info.phoneNumber as [];
// console.log(myArray, 'ARRAY');
return info.phoneNumber;
})
}```

Firestore is a "document store database". You fetch and store entire DOCUMENTS (think "JSON objects") at a time. One of the "anti-patterns" when using document store databases is thinking of them in SQL/relational DB terms. In SQL/relational DB, you "normalize" data. But in a document store database (a "NoSQL" database) we explicitly denormalize data -- that is, we duplicate data -- across documents on write operations. This way, when you fetch a document, it has all the data you need for its use cases. You typically want to avoid "JOINs" and limit the number of references/keys in your data model.
What you are showing in the code above is valid in terms of fetching documents, and extracting the phoneNumber field from each. However, use of .forEach() is likely not what you want. forEach() iterates over the given array and runs a function, but the return value of forEach() is undefined. So the return info.phoneNumber in your code is not actually doing anything.
You might instead use .map() where the return value of the map() function is a new array, containing one entry for each entry of the original array, and the value of that new array is the return value from map()'s callback parameter.
Also, mixing await and .then()/.catch() is usually not a good idea. It typically leads to unexpected outcomes. I try to use await and try/catch, and avoid .then()/.catch() as much as possible.
So I would go with something like:
try {
let querySnap = await this.afs.collection('members', ref =>
ref.where('phoneNumber', '>=', 0)).get();
let phoneNumbers = await this.getInfo(querySnap.docs[i].data());
} catch(ex) {
console.error(`EXCEPTION: ${ex.message}`);
}
getInfo(querySnapDocs) {
let arrayPhoneNumbers = querySnapDocs.map(docSnap => {
let info = doc.data();
let thePhoneNum = info.phoneNumber
console.log(`thePhoneNum is: ${thePhoneNum}`);
return thePhoneNum;
});
return arrayPhoneNumbers;
});

I solved this with help and I hope this may be helpful to others in Getting access to 1 particular field in your documents. In my service:
async getPhone() {
return await this.afs.collection('members', ref => ref.where('phoneNumber', '>=', 0))
.get().toPromise()
.then(snapshot => {
if (snapshot.empty) {
console.log('No Matches');
return;
}
return this.getInfoNum(snapshot.docs);
});
}
getInfoNum(data) {
return data.map(doc => {
let info = doc.data();
return info.phoneNumber
});
}
In my Component using typescript
phoneNumbers: string[] = [];
getPhone() {
this.dbService.getPhone().then(phoneNumbers => {
this.phoneNumbers = phoneNumbers;
this.smsGroupForm.controls.number.setValue(phoneNumbers.join(',')) //sets array seperated by commas
console.log(phoneNumbers);
});
}
This returns all the phone numbers in a comma separated array.
In my template I pull the numbers into an input for another function to send multiple text. Code in the template is not polished yet for the form, I am just getting it there for now.
<ion-list>
<ion-item *ngFor="let phoneNumber of phoneNumbers">
<ion-label position="floating">Phone Number</ion-label>
<ion-input inputmode="number"
placeholder="Phone Number"
formControlName="number"
type="number">{{ phoneNumber }}
</ion-input>
</ion-item>
</ion-list>

Related

Velo by Wix, JSON data in repeater

I'm trying to get the temperature of each hour from this website: https://www.smhi.se/vader/prognoser/ortsprognoser/q/Stockholm/2673730
I'm getting the data from https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/16/lat/58/data.json. The "t" object is the temperature.
The problem I have is displaying the data for each hour in the repeater.
Here is my backend-code:
import { getJSON } from 'wix-fetch';
export async function getWeather() {
try {
const response = await getJSON('https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/16/lat/58/data.json');
console.log(response) // all data
const tempData = response.timeSeries[0].parameters[10].values[0];
return tempData // Only returns "t" - temperature
} catch (e) {
return e;
}
}
The backend part works, however the frontend doesn't.
import { getWeather } from 'backend/getSMHI.jsw'
$w.onReady(function () {
(
getWeather().then(weatherInfo => {
$w('#weatherRepeater').onItemReady(($item, itemData, index) => {
if (index > 6) {
$item('#tempText').text = itemData.timeSeries[index].parameters[1].values[0];
} else if (index === 6) {
$item('#tempText').text = itemData.timeSeries[index].parameters[0].values[0];
} else {
$item('#tempText').text = itemData.timeSeries[index].parameters[10].values[0];
} // The parameters number for "t" changes depending on the index
})
$w('#weatherRepeater').data = weatherInfo;
})
)
})
Seems like there are at least a couple of issues here.
First, you are retrieving a single number from the API and trying to put that in a repeater. From the description of what you're trying to do, it would seem that you mean to be retrieving a list of numbers, probably as an array. You probably want to do some filtering and/or mapping on the response data instead of directly accessing a single value.
Second, the data you send to a repeater must be in the proper format. Namely, it must be an array of objects, where each object has a unique _id property value (as a string). You are not doing that here. You are simply assigning it a number.
Third, and this is just an efficiency thing, you don't need to define the onItemReady inside the then(). Not that it will really make much of a difference here.

Angular Firestore - Map data from nested array of references

I have a collection A and those documents have an array of references to documents in collection B.
In my service I get all my A's but with an array of unusable objects. I but I want to view them too.
getAs() {
this.aService.getAs().subscribe((data) => {
this.aArray = data.map((e) => {
return {
id: e.payload.doc.id,
...(e.payload.doc.data() as {}),
} as A;
});
//TODO some magic to get a nice array of B's inside every A
});
}
It's important to get the array of A objects with arrays of B object inside and not two separate arrays of A's and B's.
I hope I have expressed myself clearly to some extent.
Thanks in advance
As outlined in the Firestore documentation here Firestore Reference a document reference refers to a document location within Firestore and can be used to read, write, or listen to said document. This means that the reference type does not store the document data, and therefore you must query for it.
What you'll have to do is loop over each reference in array_of_bs and use its path to query for the corresponding document. Then, add the document data to a temporary array and combine it with the array being returned by the map() function like so:
async getAs() {
this.aService.getAs().subscribe(async (data) => {
const promises = await data.map(async (e) => {
// temp array to hold b document data
let array_of_bs = [];
// loop over each reference in array_of_bs
for (const path of (e.payload.doc.data() as any).array_of_bs) {
const bObj = await this.afs.doc(path).get().toPromise();
array_of_bs.push(bObj.data());
}
return {
id: e.payload.doc.id,
...(e.payload.doc.data() as {}),
array_of_bs: array_of_bs // this will overwrite the array_of_bs returned in the above line with the document data
} as A;
});
const docValues = await Promise.all(promises);
console.log(docValues);
});
}

Array is empty after for loop

I'm setting an array before a for loop, inside the for loop I use .push() to add data to the array but after this loop the array is empty.
MessageNotification.find({for: req.user.id}, (err, notifications) => {
var userdata = [];
notifications.forEach((notif) => {
User.findById(notif.from, (err, user) => {
userdata.push({
id: user._id,
username: user.username,
thumbnail: user.thumbnail
});
});
});
console.log(userdata);
});
As you can see on the code I am running a mongoose query to find all notifications for a specific id, then, I am setting an array to get details about the sender of each notification. Inside a forEach loop I save the results in the array. Console.log on line 12 returns an empty array [] even though User.findById on line 4 gets the User data
The problem is you are doing and asynchronous call in forEach. You should either use async/await with for..of or promises in such cases.
In your case, actually there is no need to do multiple calls on User model, you can get the desired result in a single query. Try the below code:
MessageNotification.find({
for: req.user.id
}, (err, notifications) => {
const fromArr = notifications.map(({
from
}) => from); // taking out **from** values from all notifications
User.find({
_id: {
$in: fromArr
}
}, (err, users) => { // single query to get the data
const userData = users.map(({
_id: id,
username,
thumbnail
}) => {
return {
id,
username,
thumbnail
};
});
console.log(userData);
});
});
The problem here is actually that you're calling .forEach with an async calls inside. Rather than iterating over each item in the array, and running a separate query for each, you should use the $in operator which will check if any values match items within the array, with a single query.

React Promise Return Firebase Array

I am having a really hard time solving this issue I'm currently having. You see, I am using Firebase as my database to store and fetch data.
Right now, I want to be able to return an array that is being made inside a Firebase .once call, but I am having some difficulties. This is my code so far:
Calling the function (a return function):
<p>{this.fetchStartingPrice(singleProduct.id)}</p>
This is where I want to display the specific value, that I am trying to fetch down below:
fetchStartingPrice(category){
let promises = [];
promises.push(this.fetchPrices(category));
Promise.all(promises).then(result => {
console.log(result);
})
}
I have just used a console.log in an attempt to troubleshoot errors.
fetchPrices(category){
var allPrices = [];
allProductsFirebase.child(category).once('value', (snapshot) => {
snapshot.forEach((childSnapshot) => {
if(childSnapshot.key == category){
allPrices.append(childSnapshot.val().brand);
}
});
return allPrices;
})
}
So basically, I want to loop through the allProductsFirebase in an attempt to first identify the brand of the product, and if it matches with the brand that has been used as a parameter in fetchStartingPrice() and fetchPrises(), I want to store the specific price of that product in an array of numbers (prices). After I have looped through the whole snapshot, I want to return the full array containing only product prices, and then through fetchStartingPrice(), I want to use Math.min(promises) to grab the lowest number in that array. However, I am having a really hard time doing this. Could someone please help me with this?
I want to be able to then, after all of this, return the value in fetchStartingPrice().
fetchPrices() must return Promise or be Promise. you are returning nothing from fetchPrices() ( you are returning allPrices in the .once() scope ). try to return the result ( if it returns Promise ) that .once() returns.
fetchPrices(category){
var allPrices = [];
return allProductsFirebase.child(category).once('value', (snapshot) => {
snapshot.forEach((childSnapshot) => {
if(childSnapshot.key == category){
allPrices.append(childSnapshot.val().brand);
}
});
return allPrices;
})
}

Why can't I update its element value correctly within Array.forEach() loop in Angular 7?

I'm testing Material Table(mat-table) on Angular 7, here's a weird issue I ran into.
Send a request to jsonplaceholder for fake data in users.service
export class UsersService {
API_BASE = 'https://jsonplaceholder.typicode.com/users';
constructor(private http: HttpClient) {}
getUsers(): Observable<object> {
const url = this.API_BASE;
return this.http.get(url);
}
}
Because jsonplaceholder only returns 10 rows of data, so I concatenate the data for a larger array, say, 30 rows for testing pagination feature with ease. Meanwhile, update the 'id' field with iterate index so the 'id's looks like 1,2,3...30, instead of 1,2,3...10,1,2,3...10,1,2,3...10, which is a result of concatenation, that's it, nothing special.
users.component:
ngOnInit(): void {
this.userService.getUsers().subscribe((users: UserData[]) => {
users = users.concat(users, users);
users.forEach((user, index) => (user.id = index +1));
console.log(users);
this.dataSource.data = users;
});
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
Although the table shows up beautifully, but the 'id's column looks weird, they are not 1,2,3...30 sequentially, instead, they are 21,22,23...30,21,22,23...30,21,22,23...30
I tried to print out the user.id inside the forEach loop, it's all good.
users.forEach((user, index) => {
user.id = index + 1;
console.log(user.id);
});
Where did I go wrong with this? Any clue? Thanks.
P.S, API used in the code: https://jsonplaceholder.typicode.com/users
even though you have 30 array elements after concatenating the array twice, you still only have 10 unique objects. the Object behind users[20] is the same as users[0], so you override the id of the already processed objects from index 10 to 29
you can fix this by creating a copy of each object. There are many ways too do this. a very simple way is serializing and deserializing using JSON.stringify and JSON.parse:
users.forEach(user => users.push(JSON.parse(JSON.stringify(user))));

Resources