Reduce number of operators in an RxJS mapping expression - javascript

I've create an Http request to get json data. Inside that json - there is an object which has an array. ( I need that array).
fromDb$ = of({
Result: {
Countries: [{ <--wanted array
ISOCode: 1,
Name: 'aaa'
}, {
ISOCode: 2,
Name: 'bbb'
}]
}
});
But- the data in the array has a different structure than I actually need.
I need to map (name &ISOcode) to (name and value )
This is what I've tried:
Use pluck to extract the inner Array
mergeMap the array object to a stream of objects (using of())
using map to transform each item to a desired structure
using toArray to wrap all to an array ( so I can bind it to a control)
Here is the actual code :
this.data = this.fromDb$.pipe(pluck<PtCountries, Array<Country>>('Result', 'Countries'),
mergeMap(a => from(a)),
map((c: Country) => ({
name: c.Name,
value: c.ISOCode,
})),
toArray());
The code does work and here is the online demo
Question
It looks like I've complicated it much more than it can be ,Is there a better way of doing it?

This line: mergeMap(a => from(a)) does not make a lot of sense. It's almost as if you did [1,2,3].map(v => v). You can just remove it.
To simplify this you basically need to use Array.map inside Observable.map.
Try this:
this.data = this.fromDb$.pipe(pluck<PtCountries, Array<Country>>('Result', 'Countries'),
map((countries: Country[]) => countries.map(country => ({
name: country.Name,
value: country.ISOCode,
}))));
Live demo

this.data = this.fromDb$.pipe(
mergeMap(object => object.Result.Countries),
map(country => ({ name: country.Name, value: country.ISOCode })),
toArray()
);

Related

Angular 12 how to return an http get request array of objects and assign to other array of different type using map()

Hey I'm trying to implement a bootstrap5 dropdown following this example: Creating Multi-Select Dropdown with Angular and Bootstrap 5
In that example, to get the data, he uses an app.service and just returns an array of objects:
getFoods(): Food[] {
return [
{
id: 1,
name: 'Grapes'
},
{
id: 2,
name: 'Melon'
},
...
And then in his ngOnInit() calls the getFoods() method and also uses .map() operator because he has to assign to values because the item model has two values:
ngOnInit(): void {
this.items = this.appService.getFoods().map(fruit => ({
id: fruit.id,
name: fruit.name
} as Item));
}
So I'm trying to do hat but with data being fetched from an API endpoint using HTTP GET request.
But I don't know how to use the .map() operator for the http get request:
this.subscription = this.contactSearchService.currentCodes.pipe(
map(
code => (
{
id: code.code,
name: code.code
}
)
)).subscribe(Response => {
this.items = Response
})
It's giving me these errors:
Property 'code' does not exist on type 'ResponsibilityCode[]'.
Type '{ id: any; name: any; }' is missing the following properties from type 'Item[]': length, pop, push, concat, and 26 more.
My http get request function:
private _reponsibilityCodeSource = new BehaviorSubject<ResponsibilityCode[]>([]);
currentCodes = this._reponsibilityCodeSource.asObservable();
getCodes(): void {
this.http.get<ResponsibilityCode[]>('https://localhost:44316/api/SITEContacts/ResponsibilityCode').subscribe(Response => {
this._reponsibilityCodeSource.next(Response);
});
}
I get the data as `JSON` btw.
The rxjs pipe(map(.... code...)) is different than array.map -
pipe(map()) does not operate on each item of an array
So the errors you are getting is because you're swapping out the array of ResponsibilityCode for a single item (code in your code is all the responsibility codes)
Try
this.subscription = this.contactSearchService.currentCodes.subscribe(Response => {
this.items = Response.map(
code => (
{
id: code.code,
name: code.code
}
)
)
})
Your HTTP get returns an Observable of ResponsibilityCode array, so to achieve that you have to map (Array.prototype.map) the items of the array within the RxJS's map operator, like the following:
this.subscription = this.contactSearchService.currentCodes
.pipe(
map((res: ResponsibilityCode[]) =>
res.map((item) => ({
id: item.id,
name: item.name,
}))
)
)
.subscribe((res) => {
this.items = res;
});

How to approach multiple async calls on single array?

this is bit more theoretical question. I originally intented to call this question Is it possible to iterate over map twice, but just from the sound of it, it sounds like an anti-pattern. So I assume I'm just approaching this wrong.
Also note: Data here servers as an abstraction. I know that what I'm doing here with data provided here is unnecessary, but please don't get fixated too much on data-structure and what-not. It does not represent the real (more complex, which furthermore is provided by client and I can't alter) data I'm working with. Instead approach the problem as how to return structured async calls for each array item please! :-)
My problem boils down to this:
I have array of ids on which I need to execture two separate asynchronous calls
Both of these callls need to pass (and in all id instances)
So as an example, imagine I have these two data-sets:
const dataMessages = [
{ id: "a", value: "hello" },
{ id: "b", value: "world" }
];
const dataNames = [
{ id: "a", name: "Samuel" },
{ id: "b", name: "John" },
{ id: "c", name: "Gustav"},
];
And an API-call mock-up:
const fetchFor = async (collection: Object[], id: string): Promise<Object> => {
const user = collection.find(user => user.id === id);
if (!user) {
throw Error(`User ${id} not found`);
}
return user;
};
Now I need to call the fetchFor() function for both the data-sets, presumably inside the inside the Promise.all, given forEach is not asynchronous from a predetermined array of ids.
I was thinking something akin to maping a list of Promises for the Promise.all to execute. This works fine, when you only need to map a single api-call:
const run = async () => {
const result = await Promise.all(
['a', 'b'].map(id => fetchFor(dataMessages, id)
)
console.log(result) // [{id: 'a', value: 'hello'}, {id: 'b', value: 'world}]
}
However I somehow need to return both promises for the
fetchFor(dataMessages, id)
fetchFor(dataNames, id)
inside the Promise.all array of Promises.
I guess I could always simply do a flatMap of two maps for both instances of API calls, but that sounds kinda dumb, given
I'd be doing array.map on same array twice
My data structure would not be logically connected (two separate array items for the same user, which would not even by followed by eachother)
So ideally I'd like to return dat in form of
const result = await Promise.all([...])
console.log(result)
/* [
* {id: 'a', message: 'hello', name: 'Samuel'},
* {id: 'b', message: 'world', name: 'John'},
* ]
Or do I simply have to do flatmap of promises and then do data-merging to objects based on id identifier inside a separate handler on the resolved Promise.all?
I've provided a working example of the single-api-call mockup here, so you don't have to copy-paste.
What would be the correct / common way of approaching such an issue?
You could nest Promise.all calls:
const [messages, names] = await Promise.all([
Promise.all(
['a', 'b'].map(id => fetchFor(dataMessages, id)
),
Promise.all(
['a', 'b', 'c'].map(id => fetchFor(dataNames, id)
)
]);
If you're wanting to then merge the results after retrieved, it's just a matter of standard data manipulation.

Can you dynamically deconstruct an object for usage with currying?

I've been playing with functional javascript a bit and had an idea for a util function using deconstructing.
Is it possible using ...rest to pass the names of object keys to later filter out properties?
reading through the ...rest docs I haven't seen any mention of deconstructing.
If not what solution could solve this issue?
const stripObject = attr => ({ ...attr }) => ({ ...attr });
const getUserProps = stripObject(['_id', 'firstName']);
console.log(getUserProps({ _id: 1, firstName: 'foo', lastName: 'bar' }));
/*
I understand right now whats happening is the []
passed is being ignored and its just returning a
function that passing in all the props
{
_id: 1,
firstName: 'foo'
}
*/
Just in case you like to spread stuff you could spread a specially prepared Proxy :)
const stripObject = attrs => obj => ({ ...new Proxy(obj, {
ownKeys() {
return attrs
}
})
});
const getUserProps = stripObject(['_id', 'firstName']);
console.log(getUserProps({
_id: 1,
firstName: 'foo',
lastName: 'bar'
}));
{ ...attr } in parameter position means "get all properties of the passed in object and assign it to a new object assigned to attr". I.e. you are just creating a shallow clone of the object that is passed in.
I.e. these two functions are equivalent except for the cloning part
({...foo}) => foo
foo => foo
So no, what you want is not possible (this way). You cannot declare parameters dynamically.
If you want to pull out specific props, you can do adapt this approach (One-liner to take some properties from object in ES 6) to your requirements:
const stripObject = attr => obj => pick(obj, ...attr);
After learning what I originally isn't possible solution I ended up using was to reduce over the keys initially passed then grab the prop form the object.
const stripObject = keys => obj => {
return keys.reduce((p, c) => (
{ ...p, [c]: obj[c] }
), {});
};
const getUserProps = stripObject(['_id', 'firstName']);
console.log(getUserProps({
_id: 1,
firstName: 'foo',
lastName: 'bar'
}));

Typescript : filter a table and return each mapped item

i have a an array mapping action looking like this :
this.itemsList= res.map( ( x, index ) => {
x.id = x.code;
x.itemName = x.name;
return x;
} );
I ve tried to optimize it like this (2nd manner):
this.itemsList = res.map(({code: id, name: itemName}) => ({id, itemName}));
but i need to return each mapped element of the array (return x)
i wonder how to do it using my optimized manner (2nd)
ideas ??
You can use the ... spread operator to spread the remaining object properties into a value, and the use the spread operator again to spread those properties from the stored object back into the target object.
res.map(({ code: id, name: itemName, ...otherProps }) => ({
id, itemName, ...otherProps,
}));
Note that this does remove the original code and name properties. If you still need those, you'll have to add them explicitly as well.
res.map(props => ({
id: props.code,
itemName: props.name,
...props,
}));
When you say filter I believe you are referring to the plucking of specific properties -- in this case using destructuring. This is different than the collection filter operation which removes elements from a collection based on the result of a projected function.

rxjs5 - filtering array of objects by observable that each object contains

I have an array that looks like this
[
{
name: 'foo'
filter: Observable.of(true)
},
{
name: 'bar'
filter: Observable.of(false)
}
]
and I want to return only items whose filter resolves in true, how would I do that in the most efficient and reactive way? I'm using rxjs5 beta2.
Note that it's a pseudocode for simplicity sake, in my real case filter is actually an object, that is passed to a validation function, which returns an observable that resolves in true or false.
You can flatMap each item in the array to an Observable stream that will emit a version of the item that replaced the filter property of type Observable<bool> with a bool property.
const data$ = Rx.Observable.from(arr)
// Convert each item's filter property to type bool
.flatMap(x => x.filter.map(condition => Object.assign({}, x, { filter: condition })))
// Now we can just filter over the filter property, which is of type bool.
.filter(x => x.filter)
// Each item emitted will have a filter value of true
.subscribe(x => console.log(x));
There are two things in RxJS 4 that make this easy: map and Rx.helpers.defaultComparer. first map sends each item from the iterable separately and the defaultComparer will do the deep check for you.
var comparer = Rx.helpers.defaultComparer;
const Observable = Rx.Observable;
const arr = [
{
name: 'foo',
filter: Observable.of(true)
},
{
name: 'bar',
filter: Observable.of(false)
}
];
const data$ = Observable.from(arr)
.map(each => each)
.filter(each => comparer(each.filter, Observable.of(true)))
.subscribe(x => console.log(x));
// prints Object {name: "foo", filter: FromArrayObservable} to my console
For some reason this defaultComparer is not in five at this time. Maybe there is a new name for the helper because it is not said to be deprecated in 4 or maybe it has not be migrated over yet or is not at .helpers.

Categories