subscribe to array of nested Observables - javascript

I have array containing objects each object has two props one of it is a observable value
let myArray = [{def: 'name1', value: EventEmitter_}, {def: 'name2', value: EventEmitter_}]
What im trying to do is subscribe to the Observables and and return the root Object where the change occurred
so far I only get the specific value
myArray.forEach(e => {
e.value.subscribe(e => console.log(e))
})
I tried using merge
merge(myArray).subscribe((v)=> {
console.log(v)
})
but that does not work if the Observable is nested

You could pipe the value stream.
myArray.forEach(e => {
e.value.pipe(
map(v => [e.def, v]),
).subscribe(([d, v]) => console.log(`Root ${d} returned %{v}`));
})

obs = merge(
...this.array.map((x: any) =>
x.value.pipe(map((res) => ({ def: x.def, response: res })))
)
);
You merge the "observables" transformed with a property "def", and a property "response". So you only need one subscripcion
See stackblitz

Related

How to fetch data from multiple url and store it in single array in javascript?

I have an array of Objects `filteredG`, Every single object contains some Json data about route. Objects inside filteredG can change frequently. The url I want to fetch from is changing based on route.id. If I try to map this data it isn't getting mapped inside a single array, since I try to fetch data from multiple link. Note that, there is many to many relations between routes and stoppages.
My code example:
filteredG = [{id: 1, attributes: {…}, calendarId: 0, name: 'Rupsha'}, {id: 2, attributes: {…}, calendarId: 0, name: 'Boyra'}] this array has variable length.
filteredG.forEach((route) => fetch(`/api/stoppages?routeId=${route.id}`)
.then((response) => response.json())
.then((data) => {
data.map((stoppage) => console.log(stoppage));
}));
Result:
this snippet is printing bunch of stoppages objects in the console.
What I want is to store these objects which is currently get printed in a single array. How can I make that happen?
Please help.
I hope I understand what you mean
const allData = filteredG.map((route) => fetch(`/api/stoppages?routeId=${route.id}`)
.then((response) => response.json()));
const aData = Promise.all(allData).then((arrayData) =>
arrayData.reduce((data, item) => ([...data, ...item]), []));
//result
aData.then(data => console.log(data));
if you don't care about the order in which the results get into the array, you can use this option:
Promise.all(filteredG.map(route =>
fetch(`/api/stoppages?routeId=${route.id}`)
.then(response => response.json())
)).then(results => {
...here you map the result to an array
})

Firestore: Array of references within a document into an object with array of objects (AngularFire)

I have a collection, where each single document have an array of references.
The structure in Firestore is like this:
collection1
doc1
doc2
doc3
name: string
...
refs: Array
collection2/x786123nd...
collection2/z1237683s...
...
I want to convert that into:
[
{<doc1 as object>},
{<doc2 as object},
{ name: "Document 3", refsResult:
[
{<object here mapped from reference>},
{<object here mapped from reference>},
]
}
]
Long story short, from firebase I need to get an output of a list of object where each object has a list of objects instead of references.
THE FOLLOWING SNIPPET IS NOT WORKING
I am trying to do it at service level in Angular using RxJS to transform the output, something like this:
return this.afs.collection('collection1')
.valueChanges()
.pipe(
switchMap(objects => from(objects).pipe(
switchMap(obj => from(obj.refs).pipe(
switchMap(ref => ref.get()),
map(r => ({ ...obj, refsResult: r.data() })),
tap(obj => console.log('OBJ', obj)),
)),
)),
tap(objects => console.log(objects))
);
But it seems that I only receive one object instead of a list of objects with 2. Also, it seems that the refsResult is also a single object instead of an array. I am sure I am using the switchMaps wrong, probably they are cancelling the others results or similar.
I would like to keep the solution within the chain of rxjs operators.
EDIT 1
I got it working in a hacky way, I am just sharing it here in order to give more context, and see if we can find a way to do it with RxJS.
return this.afs.collection('collection1')
.valueChanges()
.pipe(
tap(objects => {
objects.forEach(object => {
object.refsResult = [];
object.refs.forEach(ref => {
ref.get().then(d => object.refsResult.push(d.data()));
})
})
}),
tap(programs => console.log(programs))
);
The code above works but not very well, since first return the collection and then, mutates the objects to inject the items within the array. Hopefully it helps.
Help is very appreciated! :)
You can use combineLatest to create a single observable that emits an array of data.
In your case, we can use it twice in a nested way:
to handle each object in collection
to handle each ref in refs array for each object
The result is a single observable that emits the full collection of objects, each with their full collection of ref result data. You then only need a single
switchMap to handle subscribing to this single observable:
return this.afs.collection('collection1').valueChanges().pipe(
switchMap(objects => combineLatest(
objects.map(obj => combineLatest(
obj.refs.map(ref => from(ref.get()).pipe(map(r => r.data())))
).pipe(
map(refsResult => ({ ...obj, refsResult }))
)
))
))
);
For readability, I'd probably create a separate function:
function appendRefData(obj) {
return combineLatest(
obj.refs.map(ref => ref.get().pipe(map(r => r.data())))
).pipe(
map(refsResult => ({ ...obj, refsResult }))
);
}
return this.afs.collection('collection1').valueChanges().pipe(
switchMap(objects => combineLatest(objects.map(appendRefData)))
);

How to merge values inside stateless component?

Updating a state variable always replaces it instead of merging it. How can I merge it? Function addFruits receives multiple objects like:
{fruitName: banana} {fruitName: apple} {fruitName: watermelon}
How to push this fruitName's into fruits state?
My code returns only the last fruitName. But how to merge all fruitNames like array.push(val);
const [fruits, setFruits] = useState([]); // this is the state
function addFruits(value){
Api.get(`${PATH.GET_SELECTED_FRUITS}/${value}/fruits`).then(res => { // this method gives me multiple object, (like {fruitName: banana} {fruitName: apple} {fruitName: watermelon})
res.map(result => {
setProps(result.fruitName);
})
})
}
You need to combine the existing fruits array with the result from the API:
.then((res) => {
setFruits([
...fruits,
...res.map(fruit => fruit.fruitName)
]);
});
You can merge the response with the state -
function addFruits(value){
Api.get(`${PATH.GET_SELECTED_FRUITS}/${value}/fruits`).then(response => {
totalFruits = [...fruits,
...response.map(fruit => fruit.fruitName)]
setFruits(totalFruits);
})
})

Chaining rxjs operators

I have a function called fetch which is implemented like this:
fetch(): Observable<Option> {
return this.clientNotebookService.getClientNotebook()
.pipe(
map(
clientNotebook => {
this.person = _.find(clientNotebook.persons, i => i.isn === this.isn);
return {
value: this.person.address['TownIsn'],
name: this.person.address['TownName']
};
}
)
);
}
where person is of type RelatedPerson and isn if of type string. The problem with this function is that it doesn't know anything about isn, that's to say, isn should be provided to it in some way.
This value can be extracted in the following way:
this.activatedRoute.params.subscribe(
params => this.isn = params['id']
)
And I would like to do this within the fetch method by combining rxjs operators.
You can chain them like below, just extract the 'id' and pass it to fetch
const isn=this.activatedRoute.params.pipe(pluck('id'))
isn.pipe(mergeMap(isn=>fetch(isn)))
Then add a param to your fetch, then it is available inside the function
fetch(isn){ .....
You can use combineLatest that emits an array of the latest value from both streams every time either emit a value
combineLatest(
this.activatedRoute.params.pipe(map(params => params['id'])),
this.clientNotebookService.getClientNotebook()
).pipe(map(([isn, clientNotebook]) => {
this.person = clientNotebook.persons.find(p => p.isn === isn); // No need for _
return {
value: this.person.address['TownIsn'],
name: this.person.address['TownName']
};
}));

how to create state properties from an array in React?

I am trying to setState of a component from an array of values.
I have these values on filtersSelected array->
["name", "date", "state"]
I want to set these values to my state like this
myState = {
...etc,
name: null,
date: null,
state: null
}
I tried using
this.setState(previousState => ({
...previousState,
...filtersSelected: null
}))
apparently it doesn't work.Can anyone help me?
In order to spread the array into an object you first need to convert the array into an object and then you can spread the object keys into the state:
this.setState((prevState) => ({
...prevState,
...filtersSelected.reduce(function(acc, item) {
return Object.assign(acc, {[item]: null})
}, {});
}))
There are a couple things to note here. First of all, when you call setState, you do not need to provide all of the previous state. setState "merges" the specific state properties that you pass into the already existing state.
It also might be helpful to make your filters selected array an object, as you are trying to use the spread , but is by no means necessary. If you want to keep the array, you can use the code below.
let filtersSelected = ["name", "date", "state"];
this.setState({name: filtersSelected[0], date: filtersSelected[1], state: filtersSelected[2]});
Or, if you make filtersSelected into an object (which I highly recommend), you can do:
let filtersSelected = {name: "name", date: "date", state: "state"};
this.setState(filtersSelected);
Convert your array to object first then use a loop to assign values to your newly created object.
let filteredSelected = ["name", "date", "state"];
let obj;
for(let i of filteredSelected){
obj[i] = null;
}
this.setState(previousState => ({
...previousState,
obj
}))
Everyone has his own way, here are some ways I like
let data = ["name", "date", "state"];
Object.values(data).map(i => this.setState({ [i]: null }))
But I don't like to iterate setState for each element
let data = Object.assign({}, ...["name", "date", "state"].map((prop) => ({[prop]: null})))
this.setState(data)
Or you can do like so
this.setState(["name", "date", "state"].reduce( ( accumulator, currentValue ) => ({...accumulator,[ currentValue ]: null}), {} ));

Categories

Resources