Angular get response after subscribe loops end - javascript

thanks in advance for your time.
I am looping 2 services endpoints to get data, the problem that I am facing is that I would like to wait for the first subscribe to finish in order to loop the other one, but the result outside of the first subscribe is empty and if I place the second loop within the previous subscribe, I am facing duplications and/or undefined variables.
I have tried two ways
The first solution i have tried is looping the service endpoint and then looping the other service endpoint inside the first subscribe. The problem with this solution is that i am facing duplicates and/or undefinend variables.
The second solution i tried is using forkJoin, but i have two problems with this one.
The first problem i have using forkJoin is that i am getting a Pageable from my backend (using java - spring boot) to manage the pagination, so i cannot do a proper pagination because the forkJoin retrieve the Pagination in an Array, so the details are no longer useful, since it is splitted in the array.
The second problem i have using forkJoin is that everytime i click again to fetch the data it is adding the result to the old one, making endless duplicates (even when i set my arry to empty everytime i press the button)
I will share with you guys both examples i have tried and the service endpoints i am using
Service 1:
getAllQuestionsEvaluation(page: number, level: number, profu: number, idComponent: number): Observable<any> {
return this.http.get(this.urlEndpoint + 'page/' + page + '/' + level + '/' + profu + '/' + idComponent, {headers: this.headerService.addAuthHeader()}).pipe(
catchError((e) => {
this.headerService.serverError(e);
if (this.headerService.isNoAuth(e)) {
this.appDialogService.confirm();
}
return throwError(e);
})
);
}
I first loop that endpoint and then i need to loop the result for the next service to get the other data:
Service 2:
getAllByIdQuestion(idQuestion: number): Observable<any> {
return this.http.get(this.urlEndpoint + 'findbyidq/' + idQuestion, {headers: this.headerService.addAuthHeader()}).pipe(
catchError((e) => {
this.headerService.serverError(e);
if (this.headerService.isNoAuth(e)) {
this.appDialogService.confirm();
}
return throwError(e);
})
);
}
The first code i have been trying is the following, however i and facing duplicates and problems with the result:
jsonComponentsResult is the array result of components, however that service is not looping, so i am facing no problems with it and that is why i am not sharing that code.
this.questions = [];
this.questionsToCompare = [];
for (let com of jsonComponentsResult) {
this.questionService.getAllQuestionsEvaluation(0, this.evaluation.level, this.evaluation.profu, com.idComponent.idComponent).subscribe(
(jsonQuestions) => {
this.questionsToCompare = this.questionsToCompare.concat(jsonQuestions.content);
for (let i = 0; i < this.questionsToCompare.length; i++) {
this.questionsGradeService.getAllByIdQuestion(this.questionsToCompare[i].idQuestion).subscribe(
(response) => {
this.questionsGrades = this.questionsGrades.concat(response);
for (let p of this.questionsGrades) {
if (this.evaluation.idGrade.idGrade === p.idGrade.idGrade) {
this.questions = this.questions.concat(p.idQuestion);
}
}
this.dataSource = new MatTableDataSource < Questions > (this.questions);
}
);
}
this.pageIndex = jsonQuestions.number;
this.pageSize = jsonQuestions.size;
this.length = jsonQuestions.totalElements;
},
(error) => {
console.log(error);
}
);
}
My second solution is to use ForkJoin, however as i mentioned before, i am not able to manage my pagination and i am getting duplicates everytime i press the button even though i am setting the arrays to empty []:
observables: Observable < Questions > [] = [];
observablePG: Observable < QuestionsGrade > [] = [];
this.questions = [];
this.questionsToCompare = [];
this.questionsGrades = [];
for (let i = 0; i < jsonComponentResult.length; i++) {
this.observables.push(this.questionService.getAllQuestionsEvaluation(0, this.evaluation.level, this.evaluation.profu, jsonComponentResult[i].idComponent.idComponent));
}
Observable.forkJoin(this.observables).subscribe(
(response) => {
for (let x of response) {
this.questionsToCompare = this.questionsToCompare.concat(x);
}
this.observables = [];
this.getQuestions(this.questionsToCompare);
},
(e) => {
console.log(e);
}
);
private getQuestions(questionsArray: any) {
for (let i = 0; i < questionsArray.length; i++) {
this.observablePG.push(this.questionsGradeService.getAllByIdQuestion(questionsArray[i].idQuestion));
this.questions = [];
this.questionsToCompare = [];
}
Observable.forkJoin(this.observablePG).subscribe(
(response) => {
for (let x of response) {
this.questionsGrades = this.questionsGrades.concat(x);
}
this.observablePG = [];
for (let p of this.questionsGrades) {
if (this.evaluation.idGrade.idGrade === p.idGrade.idGrade) {
this.questions = this.questions.concat(p.idQuestion);
}
}
this.dataSource = new MatTableDataSource < Questions > (this.questions);
},
(e) => {
console.log(e);
}
);
}
I would like to wait for the first subscribe to finish in order to loop the other one, but i haven't been able to find a solution, since it looks like looping those services at the same time are causing troubles, and with the forkJoin i am losing my pagination and i am getting all the list again everytime i press the button.
I would like to get the questions list, then, get the questionsGrades to compare if i should add that question to my questions array.
Sorry for the long post and thank you for your time.

Maybe rxjs operator decision tree page could help you with this.
And the second, I remember that I had a similar issue, the only difference was that the value from the first Observable was used as a param in a second one, and for that I used switchMap operator.

Related

Adding more elements to an object in javascript

Context: I'm fetching 'car' data below (see the code that starts with "for") from a GET request and am pushing it to the 'array' array. And for each car.ID that I get, I need to run another GET request in sequence (the GET uses car.ID as a parameter and I have no problems in doing this).
Problem: after I fetch the results from the second GET, how to push the data to the same object of the array (i.e. I want to "complement" the object above that ended on car.BrandID by adding a few more key: value pairs to the same "line")?
THANK YOU IN ADVANCE.
for (let car of carsJustObtained) {
for (i=0; i<=2; i++){
array.push(
{
timestamp: epoch,
ID : car.ID,
BrandID : car.BrandID
})
//code continues but don't worry
FULL CODE BELOW:
function gotCars(carsJustObtained) {
for (let car of carsJustObtained) {
for (i=0; i<=2; i++){
array.push(
{
timestamp: epoch,
ID : car.ID,
BrandID : car.BrandID,
ModelID : car.ModelID,
}
);
//given car.ID the second GET will be triggered because the path depends on this variable!
let path_get_all_prices = `xxx=${car.ID}?securityToken=xxx&vehiclePriceTypeID=xxx`;
let get = https.get(
{
hostname: 'xxx.com',
path: path_get_all_prices
},
(getRes) => {
console.log(`getting prices for car ${car.ID}...`);
var reply = "";
getRes.on("data", (chunk) => (reply += chunk));
const obj = JSON.parse(reply);
gotPrices(obj.Response);
}
);
function gotPrices(pricesJustObtained) {
for (let price of pricesJustObtained){
array.push(
//how to add results to the same array of the 1st GET? There are three 'prices' for each car.ID
)};
};
};
You have to find the index of your object in your array, then you can add everything you can to this object :
array[index].name = 'Hello';
There are many ways to do this. I recommend you read about array.map()
This function lets you iterate your array and in each iteration perform the get request and extend the current element.
The key is to recognise that you are not pushing, the second time
What you are doing is reading each element of the array, and adding some information to that element.
for (let car of carsJustObtained) {
const newInfo=getFurtherInformationAboutCar(car) // this is your second getting
car.newInfo1 = newInfo.param1;
car.newInfo2 = newInfo.param2;
car.newInfo3 = newInfo.param3;
}
To answer your specific question about "merging" information
If you have one set of properties already defined for the car, and you want to merge in multiple new properties, a simple way to do it is as follows:
car = { ...car, ...objectContainingNewProperties};
If your original car was {a:2, b:3, c:4} and objectContainingNewProperties was {c: 10, d:20, e:30}, the result would be:
{ a:2,
b:3,
c:10,
d:20,
e:30 }
Any same-named properties in the second object will overwrite those in the original object.
Your second request is of course asynchronous, so by the time you get its response, you have already populated your array with all information from the first request.
I would suggest to use a promise-enabled alternative to http.get, as promises are a native feature in JavaScript that makes working with asynchronous events less messy. I will show here how it can work with node-fetch.
As fetch is natively supported in browser agents, you can run the snippet below to see the result. As a demo I have used https://jsonplaceholder.typicode.com/ as a server resource: it returns JSON for several sample datasets, including todos and users. A todo has some properties (like a title) and has a user id. A user has an email and a username. So we could make the todos-request the first request, and the users-request the second one (based on the user id received in the first). So the principle is the same as with your cars and prices.
This relies heavily on promises:
// For demo, we use these two URls:
// They both need a number following it
let url1 = "https://jsonplaceholder.typicode.com/todos/";
let url2 = "https://jsonplaceholder.typicode.com/users/";
let promises = [];
// Let's say we build an array with 5 objects:
for (let i = 1; i <= 5; i++) {
promises.push(
// Make the request
fetch(url1 + i*30)
// Parse the response as JSON
.then(resp => resp.json())
// Process this data
.then(data => {
// Create our own object from this data
let obj = {
user: data.userId,
todo: data.title
};
// Make second request, to get user's email, joining it with obj
return Promise.all([obj, fetch(url2 + obj.user)])
})
.then(([obj, resp2]) => Promise.all([obj, resp2.json()]))
// Merge the new data with the old
.then(([obj, data2]) => Object.assign(obj, {
email: data2.email,
logon: data2.username
}))
);
}
// Wait for all requests to finish...
Promise.all(promises).then(results => {
console.log(results); // The result!
});
With async/await
The above can be made even more readable, if you use the async/await syntax:
let url1 = "https://jsonplaceholder.typicode.com/todos/";
let url2 = "https://jsonplaceholder.typicode.com/users/";
async function getOne(i) {
let resp = await fetch(url1 + i*30);
let data = await resp.json();
// Create object from first request
let obj = {
user: data.userId,
todo: data.title
};
// Make second request, joining it with obj
let resp2 = await fetch(url2 + obj.user);
let data2 = await resp2.json();
return Object.assign(obj, {
email: data2.email,
logon: data2.username
});
}
let promises = [];
for (let i = 1; i <= 5; i++) {
promises.push(getOne(i));
}
Promise.all(promises).then(results => {
console.log(results);
});

What is the best/most efficient way to search for an object in an array inside another array?

I am building a simple application that stores places I've visited. I have a local express server using a db.json file as my database. I am making 2 requests and experiencing a problem.
What I'm trying to do is iterate over both arrays so that when the app loads, countries I've been too are already preselected. this seems like a super expensive call to make and has quite slow performance already
Also it's not actually doing what I want until I trigger a second re-render of the DOM and then it updates.
e.g. if I pre-select Croatia and France in the database and then load the app, none are selected. but if I then select Korea (e.g.) then in the visited list, suddenly all 3 are visible
what would be a better way to compare the arrays? considering the object keys are not necessarily the same
componentDidMount(){
axios.get('https://restcountries.eu/rest/v2/all').then((data) => {
const updatedCountries = data.data.map((country) => {
return {...country, visited: false, cities: [], showCities: false}
})
axios.get('http://localhost:3007/countries').then((countries) => {
const visitedCountries = countries.data
for (var i = 0; i < visitedCountries.length; i++){
for (var k = 0; k < updatedCountries.length; k++){
if(visitedCountries[i].name === updatedCountries[k].name){
updatedCountries[k].visited = true
}
}
}
})
this.setState({countries: updatedCountries})
})
}
Instead of using an array to store updatedCountries, you should instead use an object. That way instead of having each element of updatedCountries compare to every element of visitedCountries, you can do a constant lookup. This will change your lookup speed from (n*n) to (n).
The reason why you do not initially see any updates is because you have an async call:
axios.get('http://localhost:3007/countries')
inside of a synchronous function. As a result, you are resetting the state while you are making the get request. Instead you should chain your api calls like
axios.get('https://restcountries.eu/rest/v2/all').then((data) => {
// edit data
return axios.get('http://localhost:3007/countries')
}).then((data) => {
// run function comparing both data
this.setState({countries: updatedCountries})
})
You need update state in second request success callback function
componentDidMount(){
axios.get('https://restcountries.eu/rest/v2/all').then((data) => {
const updatedCountries = data.data.map((country) => {
return {...country, visited: false, cities: [], showCities: false}
})
axios.get('http://localhost:3007/countries').then((countries) => {
const visitedCountries = countries.data
for (var i = 0; i < visitedCountries.length; i++){
for (var k = 0; k < updatedCountries.length; k++){
if(visitedCountries[i].name === updatedCountries[k].name){
updatedCountries[k].visited = true
}
}
}
this.setState({countries: updatedCountries})
})
})
}
For efficient way to search
axios.get('http://localhost:3007/countries').then((countries) => {
let visitedCountriesName = new Set(countries.data.map(country => country.name));
updatedCountries = updatedCountries.map((country) => {
if (visitedCountriesName.has(country.name)) country.visited = true
return country
});
this.setState({countries: updatedCountries})
})

Vue.js combine data from RESP API

I need combine multple API calls on a final object, this because API have limits to be consumed, any have an idea how is possible combine multiple calls in same final object, next is an example of my code, I need all data in this.lista but is not working:
created(){
this.$http.get('/api/transactions?senderId=8642612272713533685S&limit=1&offset=000')
.then( function(res){
console.log(res.body.count);
let limit = Math.ceil(res.body.count/1000);
console.log(limit);
let m = {};
let off = 0;
for (var i = 0; i <= limit; i++) {
this.$http.get('/api/transactions?senderId=8642612272713533685S&limit=1000', {params:{offset: off}})
.then( function(data){
this.lista = { ...this.lista, ...data.body.transactions }
} )
off = off + 1000;
}
}
);
}
any help will be appreciated
Using Promise.all is most likely what you are looking for. I will write just enough code for you to understand where to go.
// Populate your array array with URLs you want to get
let urls = ["url1", "url2", "...and so on"];
// Make into web request promises
let httpReqPromises = urls.map( url => this.$http.get(url) )
// Wait for all of them to resolve
Promise.all(httpReqPromises).then(allResponses => {
// Put them all together
this.lista = allResponses.reduce((a, b) => ({...a, ...b}, {})
})
The only work I leave up to you is how you populate the url variable.

Async Javascript: Waiting for data to be processed in a for loop before proceeding to a new function

I'm having issues understanding how to work around Javascript's asynchronous behavior in a forEach loop. This issue is quite complex (sorry), but the idea of the loop is as followed:
Loop through every item in an array
Make an HTTP request from a provider script
I then need to multiply every element of the array by a constant
Assign the new array to an item in an object
After the loop, take all the arrays and add them together into one array
The data will be assigned to the indvCoinPortfolioChartData array
I'm looking for any flaws in my event loop. I believe the battle is making this task synchronous, making sure my data is assigned before aggregating data.
The issue
When I'm adding all the arrays together, ONE dataset isn't summed up (I think because it's still being processed after the function is called). There is no error, but it doesn't have all the coin data in the final aggregated array.
This is the issue I see in the aggregatePortfolioChartData function. It begins the for loop with only 2 items in the array, and then later shows 3. The third item was not processed until after the for loop started.
image of console log (logged from aggregatePortfolioChartData function)
debug log when aggregation is successful
var indivCoinPortfolioChartData = {'data': []};
for(var i = 0; i < this.storedCoins.Coins.length; i++)
{
let entry = this.storedCoins.Coins[i];
localThis._data.getChart(entry.symbol, true).subscribe(res => {localThis.generateCoinWatchlistGraph(res, entry);});
localThis._data.getChart(entry.symbol).subscribe(res => {
if(entry.holdings > 0)
{
let data = res['Data'].map((a) => (a.close * entry.holdings));
indivCoinPortfolioChartData.data.push({'coinData': data});
localThis.loadedCoinData(loader, indivCoinPortfolioChartData);
}
else
{
localThis.loadedCoinData(loader, indivCoinPortfolioChartData);
}
});
}
Loaded Coin Data
loadedCoinData(loader, indivCoinPortfolioChartData)
{
this.coinsWithData++;
if(this.coinsWithData === this.storedCoins.Coins.length - 1)
{
loader.dismiss();
this.aggregatePortfolioChartData(indivCoinPortfolioChartData);
}
}
aggregatePortfolioChartData
aggregatePortfolioChartData(indivCoinPortfolioChartData)
{
console.log(indivCoinPortfolioChartData);
var aggregatedPortfolioData = [];
if(indivCoinPortfolioChartData.data[0].coinData)
{
let dataProcessed = 0;
for(var i = 0; i < indivCoinPortfolioChartData.data[0].coinData.length; i++)
{
for(var j = 0; j< indivCoinPortfolioChartData.data.length; j++)
{
let data = indivCoinPortfolioChartData.data[j].coinData[i];
if(data)
{
aggregatedPortfolioData[i] = (aggregatedPortfolioData[i] ? aggregatedPortfolioData[i] : 0) + data;
dataProcessed++;
}
else
{
dataProcessed++;
}
}
if(dataProcessed === (indivCoinPortfolioChartData.data[0].coinData.length) * (indivCoinPortfolioChartData.data.length))
{
console.log(dataProcessed + " data points for portfolio chart");
this.displayPortfolioChart(aggregatedPortfolioData);
}
}
}
}
Thank you for helping me get through this irksome issue.

React Axios API call with array loop giving wrong order?

I was learning react and doing some axios api call with an array. I did a code on gathering data through coinmarketcap api to learn.
So, my intention was to get the prices from the api with a hardcoded array of cryptocurrency ids and push them into an array of prices. But I ran into a problem with the prices array, as the prices were all jumbled up. I was supposed to get an array in this order
[bitcoinprice, ethereumprice, stellarprice, rippleprice]
but when I ran it in the browser, the prices came randomly and not in this order, sometimes I got my order, sometimes it didn't. I used a button which onClick called the getPrice method. Does anyone know what went wrong with my code? Thanks!
constructor(){
super();
this.state = {
cryptos:["bitcoin","ethereum","stellar","ripple"],
prices:[]
};
this.getPrice = this.getPrice.bind(this);
}
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
this.state.prices.push(data.price_usd);
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
}
}
If you want to receive the data in the order of the asynchronous calls you make, you can use Promise.all, that waits until all the promises of an array get executed and are resolved, returning the values in the order they were executed.
const cryptos = ['bitcoin', 'ethereum', 'stellar', 'ripple'];
const arr = [];
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
arr.push(axios.get(cryptoUrl));
}
Promise.all(arr).then((response) =>
response.map(res => console.log(res.data[0].name, res.data[0].price_usd))
).catch((err) => console.log(err));
You could use a closure in the for loop to capture the value of i and use it as the index once the data is returned rather than using push:
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++) {
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
(function (x) {
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
var newPrices = this.state.prices;
newPrices[x] = data.price_usd;
this.setState({prices: newPrices});
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
})(i);
}
}

Categories