Merging observables and get the stream in one subscribe - request

I have an end point that returns a list of favorites, then when this list returns i get each of these favorites and send to another endpoint to get the specific information of each of these favorite:
this.favoriteData = [];
const observables = [];
favoriteList.forEach(favorite => {
observables.push(this.assetService.getAsset(favorite.symbol)
.pipe(takeWhile((response: AssetModel) => response.id !== 0)));
});
merge(...observables)
.subscribe(res => {
this.favoriteData.push(res);
this.showLoading = false;
});
As you can see the getAsset() function calls an endpoint, and it is inside an forEach, and im saving each of these response inside an array and spread this array inside a merge function of RXJS, but when i subscribe to the merged observable and append the response to the array favoriteData, the subscribe function behavior is like, binding one by one of the response data:
i want a behavior that waits all the requests to complete and then get all of the responses in one stream, im trying to use the forkJoin operator but the tslint tells that is deprecated and to use map operator, and i dont know how to use it to do what i want

You could use a combineLatest instead, which waits for all observables to complete before emitting.
combineLatest(...observables)
.subscribe(res => {
this.favoriteData.push(res);
this.showLoading = false;
});

You can use from to generate an Observable which emits each element of the base array one at a time. Then, using mergeMap and toArray, you emit all required data at the end:
const favoriteData$ = from(favoriteList).pipe(
mergeMap(favorite => this.assetService.getAsset(favorite.symbol)
.pipe(takeWhile((response: AssetModel) => response.id !== 0))),
toArray()
);

Related

Unable to understand setState of react js function paramters in a specific call?

setListOfPosts(curPosts => {
let newPosts = [...curPosts];
newPosts[newPosts.findIndex(p => p.id === postId)].alert = response.data;
}
});
//is curPosts an instance of array or complete array?? my listofPosts is an array of objects
Your setState call needs to return newPosts, and you're creating an array using the spread operator which is why it's coming back as an array of objects.
I'm not sure what your desired output is, but by adding a return function it will set the state:
setListOfPosts(curPosts => {
let newPosts = [...curPosts];
newPosts[newPosts.findIndex(p => p.id === postId)].alert = response.data;
return newPosts
}
});
This is untested but if your logic is correct should return an array of objects with the objects alert value updated.
Another option would be to do your logic before your setState call, by creating a a newState array and then simply updating the state with that new array without the use of the callback.
The callback function is useful if you want to add a new object to state array or do something that preserves the initial state, in your example you could do it without the callback like this:
// Create a copy of the state array that you can manipulate
const newPosts = [...newPosts]
if (data.response) {
// Add your logic to the state copy
newPosts[newPosts.findIndex(p => p.id === postId)].alert = response.data;
// Replace state with state copy
setListOfPosts(newPosts)
}
Again untested but hopefully this should help you understand the use of the callback function and the right way to use it.

How to get the array object from a specific history.location.pathname

My issue is that when i call the function getAllFlashCardsFromQuest(), its call all flash cards ever created in all the pages, i wanna only the objects that comes from a specific pathname, or a way to filter the cards array.
async function getCards() {
let cardsValues = await getAllFlashCardsFromQuest() as FlashCard[]
let cardsFiltered = cardsValues.filter(()=>{
return history.location.pathname === 'CriarAlternativaQuest'
})
console.log(cardsFiltered)
setCards(cardsFiltered)
}
the object look like this:
There's not much context with your code, so I can only help so much, but one thing's for sure, and that is history.location.pathname isn't changing and you're comparing it with another constant. So cardsFiltered will be either a list of true or a list of false.
Whenever you're filtering a list, you need to take each item or a property of each item and use that in your comparison.
Ex.
async function getCards() {
let cardsValues = await getAllFlashCardsFromQuest() as FlashCard[]
let cardsFiltered = cardsValues.filter((cardValueItem) => {
return cardValueItem.pathname === 'CriarAlternativaQuest'
})
}
The thing is that you need to figure out what value or property inside of your cardsValues list that you need to compare it with 'CriarAlternativaQuest'

Loop array of IDs and fetch data by Observable

I have an array of device_ids. The array is extended dynamically by selecting items on a UI.
Initially and whenever the array is updated, I need to iterate through it and fetch data for each device_id. G
Getting data is basically a database request (to Firestore) which returns an Observable. By using switchMap I fetch some data from other database requests.
In the end I'd like to have something like an array of objects / Observables with all data I can subscribe to. My goal is using Angular's async pipe in my HTML then.
Is that possible (e.g. with RxJS)? I'm not sure how to solve this...
This is how my code currently looks like:
// Init
devices$: Observable<any;
// Array of device_ids
device_ids = ['a', 'b'];
// Get device data. This will initially be called on page load.
getDeviceItemData(device_ids) {
// Map / loop device_ids
device_items.map(device_id => {
// getDeviceById() returns an Observable
return this.getDeviceById(device_id).pipe(
// Switch to request additional information like place and test_standard data
switchMap(device => {
const place$ = this.getPlaceById(device['place_id]');
const test_standard$ = this.getTestStandardById(device['test_standard_id]');
// Request all data at the same time and combine results via pipe()
return zip(place$, test_standard$)
.pipe(map(([place, test_standard]) => ({ device, place, test_standard })));
})
).subscribe(device_data => {
// Do I need to subscribe here?
// How to push device_data to my Observable devices$?
// Is using an Observable the right thing?
});
});
}
// Add device
addDevice(device_id) {
// Add device_id to array
this.device_ids.push(device_id);
// Fetch data for changed device_ids array
getDeviceItemData(this.device_ids);
}
Preferred HTML / Angular code by using async pipe:
<div *ngFor="device of devices$ | async">{{ device.name }}</div>
Thank you for any help!
This is definitely possible with rxjs. You are on the right path. I've made a small modification to your getDeviceItemData() method.
Once the device info is retrieved by id, then you can use forkJoin to execute the two calls to get place and test_standard data parallelly and then map over that data to return the object that you need which has info of device, place and test_standard as an observable. And since we're mapping over device_ids, it'll produce an array of observables containing the required objects so that you can easily club them with async pipe.
Please note: You do not have to subscribe to the devices$ because the async pipe will automatically do that for you.
Please see the below code:
// Init
devices$: Observable<{ device: any, place: any, test_standard: any }>[];
// Array of device_ids
device_ids = ['a', 'b'];
getDeviceId: (x) => Observable<any>;
getPlaceById: (x) => Observable<any>;
getTestStandardById: (x) => Observable<any>;
getDeviceItemData() {
this.devices$ = this.device_ids.map((id) =>
this.getDeviceId(id).pipe(
switchMap((device) => {
return forkJoin([
this.getPlaceById(device['place_id']),
this.getTestStandardById(device['test_standard_id'])
]).pipe(map((y) => ({ device, place: y[0], test_standard: y[1] })));
})
)
);
}
In your HTML, you'll have to do:
EDIT: Since devices$ is an array of observables, we need to iterate over individual observables and apply async pipe on every observable.
<div *ngFor="device of devices$">
<div *ngIf="device | async as device">
<div>{{ device.device.name }}</div>
<div>{{ device.place}}</div>
<div>{{ device.test_standard }}</div>
</div>
</div>
With forkJoin you can wait for all observables to complete. Alternatively you could use combineLatest which will give you the latest data combination, every time one observable emits data. This will cause more events, but will not wait for completion of all.
getDeviceItemData(device_ids) {
const arrayOfObservables = device_ids.map(device_id => {
return this.getDeviceById(device_id).pipe(...);
}
return combineLatest(arrayOfObservables); // Observable<Data[]>
}

Superagent Not Returning Value From Then

superagent.get(URL).then((res) => {
for(let i in res.body) {
if (i==='has_rejected_advisories') {
console.log(i + "="+res.body[i]);
}
}
})
.catch((err) => err.message));
My result is:
has_rejected_advisories=false
But I am not able to use res.body[i] outside this function, i.e I want superagent function to return this value in a boolean variable to use it elsewhere.
ex.
a = superagent.get(URL).then((res) => {
for(let i in res.body) {
if(i==='has_rejected_advisories') {
console.log(i + "="+res.body[i]);
}
}
})
.catch((err) => err.message));
if(a===false){/*do this*/}
This is because the superagent.get(url) call is asynchronous. The value given to a is a Promise
Since this is async, the if (a === false) is actually executing before the function body passed to .then. You will either need to move this logic to the .then function, or use something like async/await if you like the synchronous looking syntax.
On top of jerelmiller's great advice you need to note the following:
Try this:
create a global var assuming it's a string
var mysares = ""
This example will only bring back 1 string back of everything!! Not single element. Also if you can't get the standard Fetch() to work don't try other methods like axios or superagents. Now use our global like so:
superagent.get(URL).then((res) => {
for(let i in res.body) {
if (i==='has_rejected_advisories') {
//Add comments as will help you
//to explain to yourself and others
//what you're trying to do
//That said if this iteration produces
//correct data then you're fine
//push my result as a string
mysares = res.body[i];
//infact what's in row 1?
mysares = res.body[0];
//Actually I code my own JSON!!
mysares = res.body[1];
console.log(i + "="+mysares);
}
}
})
.catch((err) => err.message));
Now you can do whatever:
if(mysares===false){/*do this*/
alert(playDuckHunt());}
Things to note:
res.body[i] is an iteration
You cannot use it outside of the function
Because:
It's local to that function
You don't know what position of 'i' is even if you could use it as you will be outside of your loop
One last thing:
Loops loop through loops or arrays etc.
So (in real world) you can't just request the value of the loop
unless you agree the position of data to be released,
type,and bucket (where it's going to be displayed or not).
Hope this helps!
PS> we need to know where 'has_rejected_advisories' is in the JSON so send us your json url as it must be a column/obj header name. Or it's any old 'a' then var a can be your "false"
In constructor:
this.state = {a:null};
In some function:
superagent.get(URL).then(
(res) => {for(let i in res.body)
{
if(i === 'has_rejected_advisories')
{
this.setState({a:res.body[i]})
}
}
}).catch((err)=>(err.message));
In render:
console.log(this.state.a);
Inside then() the value could be used using state variable but there are many scenarios we could not use them, like if we want to perform all the operations under constructor i.e Initializing state variable, calling superagent and changing the state variable and using the state variable.

Can't access element of Array

I'm adding elements to an array using a service, which successfully adds the elements. I can see the data populated with a console.log
I can't however access the element.
this.routingService.getNode(startNode).subscribe(node => {
node.forEach(node => {
this.openSet.push(new MapNode(node.id, node.lon, node.lat, this.routingService.getNodeNeighbours(node.id)));
});
});
console.log(this.openSet); // this prints out the below screenshot
However, when I use something like:
console.log(this.openSet[0]);
I get output of 'undefined'. I'm not sure if I'm being really thick right now in how I'm accessing it or not...
Any ideas?
subscribe works asynchron, so console.log() will log before the forEach loop runs. It's the same async behaviour as in this little piece of code
let foo = [];
setTimeout(() => {
foo.push(1);
foo.push(2);
}, 500);
console.log(foo); // logs []
Please see the duplicate post for options on how to work with asynchronity.
How to return the response from an asynchronous call?

Resources