Angular - Observer map not working as intended - javascript

I'm trying to fetch data from my backend, creating a class object for each item I get
getRankingList(type: RankingListType, page: number) {
let params = new HttpParams().set("pid", String(page)).set("limit", String(5));
return this.http.get(`http://127.0.0.1:3333/ranking/player/all`, { params })
.pipe(
map(item => new RankingGuild(item['guild'], item['name'], item['country'], item['honor'], item['RawKey']))
);
}
The data I'm receiving from the backend looks like this:
[
{
"RawKey": "1",
"honor": 0,
"guild": "Test",
"name": "test",
"country": 1
},
{
"RawKey": "2",
"honor": 0,
"guild": "Test2",
"name": "test2",
"country": 1
}
]
But instead of iterating through the object, "item" is the object itself, meaning there is only one iteration returning the object that I had in the first place, rather than its entries. I've been searching for hours to find a solution, but it seems like this is the correct way to do it, not sure why it doesn't work.

This is because the RxJS map operator and JavaScript's Array.map() are 2 different things altogether. You should read up on their differences.
In short, the RxJS map operator allows you to apply a given project function to each value emitted by the source Observable, and emit the resulting values as an Observable. On the other hand, the Array.map() method merely creates a new array with the results of calling a provided function on every element in the calling array.
If you want to map the value returned by the response from the HTTP request, I believe this is what you should be doing instead.
getRankingList(type: RankingListType, page: number) {
let params = new HttpParams().set("pid", String(page)).set("limit", String(5));
return this.http.get(`http://127.0.0.1:3333/ranking/player/all`, { params })
.pipe(
map(response => response.map(item => new RankingGuild(item['guild'], item['name'], item['country'], item['honor'], item['RawKey'])))
);
}
Then, on your component itself, you may subscribe to the method to return the actual values itself.
getRankingList.subscribe(res => {
// do the rest
})

The rx map operator is not the array map operator. The array map transforms items in an array, the rx map transforms items in a stream, and the item in the stream in this case is an array. Do this:
return this.http.get(`http://127.0.0.1:3333/ranking/player/all`, { params })
.pipe(
map(items => items.map(item => new RankingGuild(item['guild'], item['name'], item['country'], item['honor'], item['RawKey'])))
);
Use the array map inside your rx map.

Related

Getting error of fetched items after refreshing the page

I am fetching my data from external API as usual and this is the typical way I do it:
Fetch API:
const [tshirts, setTshirts] = useState([]);
const fetchData = () => {
fetch('apiEndpoint')
.then((response) => response.json())
.then((data) => {
setTshirts(data[0].clothes.regular.top); // path to my array
})
.catch((error) => {
console.log(error);
});
};
React.useEffect(() => {
fetchData();
}, []);
Map through an array:
const tshirtArray = tshirts.tShirt; // specifying the path
const listItems = tshirtArray.map((item) => <li>{item}</li>);
<ul>{listItems}</ul>
Example of data structure:
[
{
id: 1,
clothes: {
regular: {
top: {
sleeveless: [],
tShirt: [
"image-path-here"
],
.....
.....
.....
When I first time execute the code it works, but after some time or after refreshing the page I get an error of TypeError: Cannot read properties of undefined (reading 'map')
Why is that undefined? The path is correct and fetching the array should be as well. Can not find the reason of it not working.
I don't have reputation to comment, so let me try to clarify it for you through an answer. As #sojin mentioned, you cannot use tshirts.Tshirt since your state is of array type and arrays can't be used like objects, meaning that if there was an object of lets say exampleObject = { type: "shirt", color: "white } you could call it with exampleObject.type. Since you have an array of objects in your state (top that you are saving to state is still object which contains tShirt array), you first have to use index (to tell which object you want to use from the state array) and then you can use it like you wanted. For example, in your example there are 1 objects in state array. Array indexes start at 0. So you could do tshirts[0].tShirt to get the tShirt array from that object.
However, I would edit your code a bit. Instead of using tshirtArray constant, just do listItems from your state:
const listItems = tshirts.map((item) => {item.tShirt[0]});
Note: I've just used index 0 here to demonstrate the finding of the first item in tShirt array. If you want to see all tShirt image paths, then you may need to do nested mapping or other similar solutions.

Can you return an array of objects, with each object containing an observable from forkJoin?

let fruit = {
id: 1,
isGood: false
}
lets say I have a function call that accepts an id of a fruit object and returns an observable that resolves into a boolean indicating whether the value isGood is true or false.
I want to map through an array of fruit objects and call the function described above then use the value returned to set isGood on an object and set an id from the fruit on the same object that is returned.
In my mind, I should be able to do something like this:
forkJoin(
fruitArray.map(fruit => {
return of({
"data": isFruitGood(fruit.id)),
"id": fruid.id
})
})
).subscribe(result => console.log(result))
The problem here is that an array of objects is returned, but the observable set to data in the object is not resolving.
Your problem is that your forkJoin is unwrapping your of observable, but not the inner observable returned in your "data" property.
You need to pass forkJoin an array of the observables that get the isFruitGood value for you, then after those come back, build your objects:
forkJoin(fruitArray.map(
fruit => isFruitGood(fruit.id)
)).pipe(
map(isGoodArray => isGoodArray.map(
(isGood, i) => ({
data: isGood,
id: fruitArray[i].id
})
)
)).subscribe(result => console.log(result))
here's a sample StackBlitz

React How to merge an array and an array of objects

I have this two states:
stateOne: [
'marca01',
'marca02'
]
stateTwo: [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman'
}
]
And I'm using lodash to merge both in a third state, but as you see the first state is an array and the second one is an array of objects so I can't merge these two like I want to...
Something like this:
stateThree: [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men',
MARK:'marca01'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman',
MARK:'marca02'
}
]
How can I get the desired result (is not necessary to use lodash)
Without lodash :
const state3 = state2.map((product, index) => ({
...product,
MARK: state1[index]
}));
Here .map returns a new array whose new values are returned by the anonymous function.
The spread operator ...product flattens the previous object properties in the new object. This syntaxic sugar can be replaced by Object.assign.
Then we add a new MARK property based on the other state with the same index.
I used the map method and the spread operator in order to prevent state mutation which can be harmful in React.
You could iterate over the second array, and add the properties.
let s1 = [
'marca01',
'marca02'
]
let s2 = [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman'
}]
s2.forEach((x,i) => x.MARK = s1[i])
console.log(s2)
You can do like this:
stateThree = stateTwo.map( (element, i) => ({...element, MARK: stateOne[i] } ))

rxjs Operator that converts Observable<List<X>> to Observable<X>

I'm working with a no-so user-friendly API using Angular HTTP and rxjs. I'm trying to get return an Observable<List<Object>> from my service. Here's a sample of the JSON that's returned from my get request:
Items: {
​​ "$type": "System.Collections.Generic.List`1[[Asi.Soa.Core.DataContracts.GenericEntityData, Asi.Contracts]], mscorlib"
​​ "$values": [
Object { "$type": "Asi.Soa.Core.DataContracts.GenericEntityData, Asi.Contracts", EntityTypeName: "7", Properties: {…} }
Object { "$type": "Asi.Soa.Core.DataContracts.GenericEntityData, Asi.Contracts", EntityTypeName: "7", Properties: {…} }
Object { "$type": "Asi.Soa.Core.DataContracts.GenericEntityData, Asi.Contracts", EntityTypeName: "7", Properties: {…} }
]
}
And then inside each Object:
{
"$type": "Asi.Soa.Core.DataContracts.GenericEntityData, Asi.Contracts"
EntityTypeName: "7"
Properties: {
"$type": "Asi.Soa.Core.DataContracts.GenericPropertyDataCollection, Asi.Contracts"
"$values": {}
}
I need to extract out $values for each item, and then return a list of the $values objects. But I can't figure out how to do that with Angular and rxjs without subscribing. And I don't want to subscribe, as I want the component, not the service, to process the resulting data.
I've been looking in rxjs Operators as a solution to my problem. But there doesn't seem to be an operator that converts a List<X> into X while preserving the Observable.
Is this possible to do with Angular2+ and rxjs? Or is there maybe a better way to accomplish this task. I'm relatively new to reactive programming, so any general advice would be appreciated.
Use Observable#mergeMap (often called flatMap in other implementations).
mergeMap allows you to:
map each of your items into lists (that is, emit a list of values for each event)
flatten the lists together back into one stream of events
Assuming the following data received:
const result = {
items: {
$type: 'something something',
$values: [1, 2, 3]
}
}
You could get an Observable of its values using the following:
Rx.Observable.of(result).mergeMap(x => x.items.$values)
For instance:
Rx.Observable
.of(result)
.mergeMap(x => x.items.$values)
.subscribe(value => console.log(`new value: ${value}`));
... Would print out:
new value: 1
new value: 2
new value: 3
You transform elements in RxJS with the map()-operator. It applies a change to every element in the stream and returns the changed one.
The purpose of subscribe() is to make all the RXJS-functions being triggered in the first place. Because even when a cold observable gets a new value, nothing happens to it unless at the end of it all is a subscription. So you can do
const sub = $stream.map(originalValue => {return transformToX(originalValue);})
.subscribe(transformedValue => {
emitter.emit(transformedValue);
});
Or to demonstrate the purpose of subscribe more clearly:
const sub = $stream.map(originalValue => {return transformToX(originalValue);})
.do(transformedValue => {
emitter.emit(transformedValue);
})
.subscribe();
Do not forget to unsubscribe from all subscribtions when you don't need them anymore! See http://brianflove.com/2016/12/11/anguar-2-unsubscribe-observables/ for that.

Flatten observable array in Angular2

I'm using Firebase in my Angular2 app to retrieve data from my database. What's the easiest was of returning an array of unique values in the nested tags list? I.e. in my example I want to return ["Sport", "Adventure", "Music"] where the 2nd "Adventure" is omitted.
{
"images": {
"image1": {
"path": "path",
"date": 43534532123,
"tags": {
0: "Sport",
1: "Adventure"
}
},
"image2": {
"path": "path",
"date": 43534532123,
"tags": {
0: "Music",
1: "Adventure"
}
}
}
I tried this approach but it seems to split the elements from the first image only
return this.af.database.list('/photos/user')
.map(photos => photos.map(photo => photo.tags))
.concatAll()
.distinct()
However, this approach yields the correct output but as a separate stream of the unique tags instead of as one array
return this.af.database.list('/photos/user')
.map(photos => photos.map(photo => photo.tags))
.mergeAll()
.mergeAll()
.distinct()
UPDATE
I assumed in the original answer that it was a stream with the individual items. Later the OP clarified it is a stream of a list of photos. In this case we use Array#reduce instead of Observable#reduce.
return this.af.database.list('/photos/user')
.map(photos => photos.map(photo => photo.tags)
.reduce((allTags, current) => {
return allTags.concat(
current.filter(item => allTags.indexOf(item) === -1))
}, []))
ORIGINAL ANSWER
distinct over an observable returns unique individual values in an stream, but is not the operator we want here. It's is possible to produce a sequence with all the tags, each one as a separate value, but then we would need to regroup them again.
We can use instead reduce, quite similar to the Array counterpart. It takes an initial value ([]) and accumulates the others. We built a list with the individual values on each iteration. After the reduce we have an array of unique tags.
Notice that .list() should complete the observable for it to work.
return this.af.database.list('/photos/user')
.map(photos => photos.map(photo => photo.tags))
.reduce((allTags, current) => {
return allTags.concat(
current.filter(item => allTags.indexOf(item) === -1))
}, [])

Categories