How to get Observable to return an array of transformed elements (Rxjs)
I have an endpoint that creates a json product list. I have my own class type defined in my code for products. I am trying to get data from an endpoint and get a json array of products converted to an array of Product class.
Sample API json (simplified from my actual data):
{
"products": [{
"id": 1,
"name": "Product 1",
"materials": [{
"id": 100,
"desc": "wood"
}, {
"id": 101,
"desc": "metal"
}]
}, {
"id": 2,
"name": "Product 2",
"materials": [{
"id": 100,
"desc": "wood"
}, {
"id": 102,
"desc": "brick"
}]
}]
}
My code:
loadProducts(){
this.fetchProducts().subscribe(data => {
console.log("the data:", data);
})
}
fetchProducts(): Observable<Product[]> {
return this.http.get("http://example.com/mydata.json")
.map((response) => {
const products = (response.json()).products;
console.log("Converting to json" + products.length);
return products;
})
.map( data => {
console.log("Working on data: ", data);
return new Product(data.id, data.name, data.materials);
});
What I expect to see on my console is ...
"Converting to json 2"
"Working on data: " [object]
"Working on data: " [object]
"the data:" [object,object]
.. but what I see is ..
"Converting to json 2"
"Working on data: " [object,object]
"the data:" [object,object]
I thought the .map function would run for every item posted. I see that when I first call .map that it fires once on one item (response) it has - and I know there are products returned by two items. I would expect the second map function to execute twice - once for each product item. Instead, it appears to have been called once on the array of products.
To complicate matters, I also want to convert the list of materials to the type of the Material class I created. I know that I could do all of this with forEach loops, but I want to do it React.
source to share
Finally, I found the right combination of Observable operations to get what I was looking for.
fetchProducts(): Observable<Product[]> {
return this.http.get("http://examples.com/myData")
.map((response) => {
return response.json().products;
})
.switchMap( productArray => {
return Observable.from(productArray);
})
.map( (productData: any) => {
return new Product(
productData.id,
productData.name,
productData.materials
);
})
.toArray();
}
I misunderstood how Observable.map works - thinking that it will run on every item in my data when it actually runs on every piece of data .. and I got one piece of data to it - an array. Thanks @jonrsharpe for helping me out there.
By using switchMap to return a new Observable from my array, I was then able to then delete every piece of data in my array each time. Thanks @ giora-guttsait for this help.
Finally, I needed to concatenate all these new parts of the stream back into one array. Observable.toArray () did it for me.
source to share
In your case, you end up with something like this:
this.http.get(...)
.map(res => res.json().products) // turns this into Observable<Products[]>
.map(products => ... ) // here, products is already an array.
If you want to process this array in the same way as in React, that is, there is something to do for each product, you can do something like:
this.http.get(...)
.map(res => res.json().products)
.switchMap(products => Observable.from(products))
Here Observable.from(products)
returns Observable<Product>
and switchMap
makes the return chain Observable<Product>
you got earlier.
this.http.get(...)
.map(res => res.json().products)
.switchMap(products => Observable.from(products))
.subscribe(product => console.log(product))
Will print each of these products.
Disclamer: I haven't tested this code by running it, you may need a different statement and then switchMap
but this is what I remember works
source to share