Velo by Wix, JSON data in repeater - arrays

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.

Related

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

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

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>

How to find a user in different collections in Firestore using ReactJS?

I have two collections: users_unprocessed and users_processed. When a user is new, he will be added to the users_unprocessed collection. If he is processed, he will be deleted and added to the users_processed.
I want to create a list with all users. Therefore I need to find a user in users_processed or users_unprocessed. The list should be reactively and show live updates, therefor I need to use .onSnapshot().
database.collection("users_unprocessed").doc(id).onSnapshot((snapshot) => {
if (snapshot.numChildren > 0) {
setFetchedUser(snapshot.data());
} else {
database.collection('users_unprocessed').doc(id).onSnapshot((snap) => {
if (snapshot.numChildren > 0) {
assignUser(snap)
} else {
// Error Handling
}
})
}
This code is not giving my any result no matter of the doc exists in the users_unprocessed or users_processed.
If both collections are correctly set you just need to use the forEach function on each of them and put them on an array or list or whatever you want. Something like that:
const allUsers = [];
database.collection("users_unprocessed").onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
allUsers.push(doc.data().name);
});
});
And then you can do the same with the other collection in the same list. If you don't want to put them on an Array but you have any other method or function you just need to change the last part.

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

Is there any way to mock #defer with react-apollo?

For example, if I want to get a list of planets and the orbital length of them I can do this:
query Planets {
planets {
id
name
orbitalLength
}
}
But let's say it takes a long time to calculate the orbital length and it sometimes throws an error, so I don't want to wait for those values with the first render.
#defer would be a great solution for this problem but it isn't implemented yet, but I could load them one by one and display as they arrive, but I'm not sure, how should it be done.
Maybe something similar could work (but compose won't accept a function as a parameter):
const Feeder = graphql(gql`{
planets {
id
name
}
}`)
const Feeder2 = Feeder(compose(props => (props.data.planets || []).map(planet => {
return graphql(gql`{
orbitalLength(idPlanet: $idPlanet)
}`, {
name: `orbitalLength-${planet.id}`,
options: {
variables: {
idPlanet: planet.id
}
}
})
})))
const FeededComponent = Feeder2(Component)

Resources