RxJS - Properly merging two arrays from two seperate $http streams - javascript

Currently learning RxJS, I'm not gonna lie, I had difficulties understanding it, especially in terms of "Why do we even want to use if we have promises"
But now I think I made small step further.
I know I should avoid nested subscriptions.
Trying to write as short as possible code to merge two streams result into single variable.
So I have two arrays simulating result of streams, which I want to join.
Fights array should became new obj array inside boxers objects
const boxers = [
{
user_id:1,
first_name:'Lennox ',
last_name:'Lewis',
nationality:"UK"
},
{
user_id:2,
first_name:'Mike',
last_name:'Tyson',
nationality:'USA'
},
{
user_id:3,
first_name:'Riddick',
last_name:'Bowe',
nationality:'USA'
},
];
const fights = [
{
fight_id:1,
user_id:1,
opponnent_id:2,
winner_id:1,
venue:'Memphis Pyramid, Tennessee'
}
]
And then I wrote code:
const boxersWithFights2 = boxersStream.pipe(
flatMap(boxers => {
return fightsStream.pipe(
flatMap(fights => {
boxers.map(boxer => boxer.fights = fights.filter(fight => fight.user_id === boxer.user_id ||fight.opponnent_id === boxer.user_id ))
return boxers;
})
)
}
));
Surprisingly this works as expected.
When I subscribe to boxersWithFights, it console.logs me with properly mapped objects.
So it probably also work when returned from external api, but then I would of course need another map() operator.
My question: Is this code written well? Can it be written to be more clean & elegant ?
I also know I could do that easier with e.g. forkJoin, but I really wanted to test flatMap(mergeMap) operator.

You shouldn't mutate data in the stream but boxer.fights = does it.
Also you can combine the streams together via forkJoin because they don't depend on each other.
Try to use map operator instead:
const boxersWithFights2 = forkJoin([boxersStream, fightsStream]).pipe(
map(([boxers, fights]) => {
return boxers.map(boxer => ({
...boxer,
fights: fights.filter(fight => fight.user_id === boxer.user_id ||fight.opponnent_id === boxer.user_id ),
})),
));

Related

Map and pushing arrays

I am quite new to React and TypeScript. I have some icons I want to map like this:
const iconLookups =
dataPackNumber1.map(
(e) =>
e.icon_prefix &&
e.icon_name && {
prefix: e.icon_prefix,
iconName: e.icon_name,
},
) as IconLookup[];
Further I have more icons under dataPackNumber2 and dataPackNumber3 that look the same and I would like to map them all at once. Another way I was thinking of was to map them seperately and then to push them into the iconLookups array, but I cant seem to figure out how.
iconLookups.push(
dataPackNumber.map(
(e) =>
e.icon_prefix &&
e.icon_name && {
prefix: e.icon_prefix,
iconName: e.icon_name,
},
) as IconLookup[];)
and
const iconLookups =
dataPackNumber1 && dataPackNumber2 && dataPackNumber3.map(
(e) =>
e.icon_prefix &&
e.icon_name && {
prefix: e.icon_prefix,
iconName: e.icon_name,
},
) as IconLookup[];
The code doesn't provide me errors, but on the UI I can see that only the last provided dataPackNumber will be actually rendered, if I chain them with &&.
Can someone please enlighten me?
.push() will push one element onto the array. In your case, that element is an entire array. Resulting in a structure like this:
[1, 2, [3, 4, 5]]
&& will likely just resolve to the last expression, which in your case is just dataPackNumber3.map(/*...*/).
One way to combine all three is with the spread syntax. Structurally it would be something like:
let result = [...array1, ...array2, ...array3];
So in your case it might be:
const iconLookups = [
...dataPackNumber1,
...dataPackNumber2,
...dataPackNumber3.map(/*...*/)
] as IconLookup[];
Edit: As pointed out in a comment below, .push() can indeed add multiple elements to an array when added as multiple arguments:
arr1.push(3, 4, 5);
Which means the spread syntax can also work there:
const iconLookups = dataPackNumber1.map(/*...*/) as IconLookup[];
iconLookups.push(...dataPackNumber2.map(/*...*/));
iconLookups.push(...dataPackNumber3.map(/*...*/));

Filter an Array of Objects from an Array in TypeScript

I built a custom component that filters an array of objects. The filter uses buttons, sets from active to non-active and allows more than one option on/off at the same time.
StackBlitz of my attempt - https://stackblitz.com/edit/timeline-angular-7-ut6fxu
In my demo you will see 3 buttons/options of north, south and east. By clicking on one you make it active and the result should include or exclude a matching "location" either north, south and east.
I have created my methods and structure to do the filtering, I'm struggling with the final piece of logic.
So far I have created a method to create an array of filtered locations depending on what the user clicks from the 3 buttons.
Next this passes to my "filter array" that gets the logic that should compare this filtered array against the original to bring back the array of results that are still remaining.
Its not quite working and not sure why - I originally got this piece of functionality working by using a pipe, but fore reasons do not want to go in that direction.
//the action
toggle(location) {
let indexLocation = this.filteredLocations.indexOf(location);
if (indexLocation >= 0) {
this.filteredLocations = this.filteredLocations.filter(
i => i !== location
);
} else {
this.filteredLocations.push({ location });
}
this.filterTimeLine();
}
// the filter
filterTimeLine() {
this.filteredTimeline = this.timeLine.filter(x =>
this.contactMethodFilter(x)
);
}
//the logic
private contactMethodFilter(entry) {
const myArrayFiltered = this.timeLine.filter(el => {
return this.filteredLocations.some(f => {
return f.location === el.location;
});
});
}
https://stackblitz.com/edit/timeline-angular-7-ut6fxu
Sorry for my expression but u have a disaster in your code. jajaja!. maybe u lost that what u need but the logic in your functions in so wrong. comparing string with objects. filter a array that filter the same array inside... soo u need make a few changes.
One:
this.filteredLocations.push({location});
Your are pushing object. u need push only the string.
this.filteredLocations.push(location);
Two:
filterTimeLine() {
this.filteredTimeline = this.timeLine.filter(x =>
this.contactMethodFilter(x)
);
}
in this function you filter the timeLine array. and inside of contactMethodFilter you call filter method to timeLine again....
See a functional solution:
https://stackblitz.com/edit/timeline-angular-7-rg7k3j
private contactMethodFilter(entry) {
const myArrayFiltered = this.timeLine.filter(el => {
return this.filteredLocations.some(f => {
return f.location === el.location;
});
});
}
This function is not returning any value and is passed to the .filter
Consider returning a boolean based on your logic. Currently the filter gets undefined(falsy) and everything would be filtered out

Use Fluture with Ramda

I was using Bluebird for doing asynchronous stuff, but now have to do a lot of empty / null / error checks and I don't want to go down the usual if Else route. Am thinking of using monads, but have not yet grokked it completely.
Also I want it to play nicely with ramda's pipe / compose as most of my other code is neatly encapsulated in functional pipelines. According to many discussions, monadic Futures (Fluture seems to be recommended) are preferred over Promises and support for pipeP and composeP may be removed in future versions.
Fluture seems like a good option as it supposedly plays well with libraries (like ramda) that adhere to fantasy-land specs.
However I am completely lost as to how to go about implementing stuff integrating Ramda's pipe with Fluture. I need help with some example code.
For eg:
I have a DB call that returns an array of Objects. The array may have values, be empty or be undefined. I have a functional pipeline that transforms the data and returns it to the front end.
Sample Promise code:
fancyDBCall1(constraints)
.then(data => {
if (!data || data.length === 0) {
return []
}
return pipe(
...
transformation functions
...
)(data)
})
.then(res.ok)
.catch(res.serverError)
Can somebody give some pointers on a good way to proceed.
What can you do ?
So, there are a few things when can do with your code. But first, let's talk about Monads.
In this code there are 3 types of Monads you can use:
Maybe ( the DB may return something, or nothing )
Either ( If some data validation fails for example )
Fluture ( to replace the promise. Flutures are different from promises! )
Maybe this, maybe not!
Let's decompose your code a little bit. The first thing we want to do is to make sure your fancyDBCall1(constraints) returns a Maybe. This means that it maybe returns a result, or nothing.
However, your fancyDBCall1 is an async operation. This means that it must return a Future. The trick here is instead of making it return a future of a value, like Future <Array> to make it return a Future < Maybe Array >.
Whoa, that sounds complicated mister!
Just think of it like instead of having: Future.of('world');
You have: Future.of( Maybe( 'world' ) );
Not so bad right?
This way you avoid doing null checks in your code! The following lines would disappear:
if (!data || data.length === 0) {
return []
}
And your example would look something like:
/*
* Accepts <Maybe Array>.
* Most ramda.js functions are FL compatible, so this function
* would probably remain unchanged.
**/
const tranform = pipe( .... );
// fancyDBCall1 returns `Future <Maybe Array>`
fancyDBCall1(constraints)
.map( transform )
.fork( always(res.serverError), always(res.ok) );
See how nice our code looks? But wait, there is more!
We Either go further, or we don't!
So, if you are paying close attention, you know I am missing something. Sure, we are now handling a null check, but what if transform blows up? Well, you will say "We send res.serverError".
Ok. That's fair. But what if the transform function fails because of an invalid username, for example?
You will say your server blew up, but it wasn't exactly true. Your async query was fine, but the data we got wans't. This is something we could anticipate, it's not like a meteor hit our server farm, it's just that some user gave us an invalid e-mail and we need to tell him!
The trick here would be go change our transform function:
/*
* Accepts <Maybe Array>.
* Returns <Maybe <Either String, Array> >
**/
const tranform = pipe( .... );
Wow, Jesus bananas! What is this dark magic?
Here we say that our transform maybe returns Nothing or maybe it returns an Either. This Either is either a string ( left branch is always the error ) or an array of values ( right branch is always the correct result ! ).
Putting it all together
So yeah, it has been quite a hell of a trip, wouldn't you say? To give you some concrete code for you to sink your teeth in, here is what some code with these constructs could possibly look like:
First we have a go with Future <Maybe Array>:
const { Future } = require("fluture");
const S = require("sanctuary");
const transform = S.map(
S.pipe( [ S.trim, S.toUpper ] )
);
const queryResult = Future.of(
S.Just( [" heello", " world!"] )
);
//const queryResult2 = Future.of( S.Nothing );
const execute =
queryResult
.map( S.map( transform ) )
.fork(
console.error,
res => console.log( S.fromMaybe( [] ) ( res ) )
);
You can play around with queryResult and queryResult2. This should give you a good idea of what the Maybe monad can do.
Note that in this case I am using Sanctuary, which is a purist version of Ramda, because of it's Maybe type, but you could use any Maybe type library and be happy with it, the idea of the code would be the same.
Now, let's add Either.
First let's focus on our transformation function, which I have modified a little:
const validateGreet = array =>
array.includes("HELLO") ?
S.Right( array ) :
S.Left( "Invalid Greeting!" );
// Receives an array, and returns Either <String, Array>
const transform = S.pipe( [
S.map( S.pipe( [ S.trim, S.toUpper ] ) ),
validateGreet
] );
So far so good. If the array obeys our conditions, we return the right branch of Either with the array, is not the left branch with an error.
Now, let's add this to our previous example, which will return a Future <Maybe <Either <String, Array>>>.
const { Future } = require("fluture");
const S = require("sanctuary");
const validateGreet = array =>
array.includes("HELLO") ?
S.Right( array ) :
S.Left( "Invalid Greeting!" );
// Receives an array, and returns Either <String, Array>
const transform = S.pipe( [
S.map( S.pipe( [ S.trim, S.toUpper ] ) ),
validateGreet
] );
//Play with me!
const queryResult = Future.of(
S.Just( [" heello", " world!"] )
);
//Play with me!
//const queryResult = Future.of( S.Nothing );
const execute =
queryResult
.map( S.map( transform ) )
.fork(
err => {
console.error(`The end is near!: ${err}`);
process.exit(1);
},
res => {
// fromMaybe: https://sanctuary.js.org/#fromMaybe
const maybeResult = S.fromMaybe( S.Right([]) ) (res);
//https://sanctuary.js.org/#either
S.either( console.error ) ( console.log ) ( maybeResult )
}
);
So, what this tells us?
If we get an exception ( something not anticipated ) we print The end is near!: ${err} and we cleanly exit the app.
If our DB returns nothing we print [].
If the DB does return something and that something is invalid, we print "Invalid Greeting!".
If the DB returns something decent, we print it!
Jesus Bananas, this is a lot!
Well, yeah. If you are starting with Maybe, Either and Flutures, you have a lot of concepts to learn and it's normal to feel overwhelmed.
I personally don't know any good and active Maybe / Either library for Ramda, ( perhaps you can try the Maybe / Result types from Folktale ? ) and that is why i used Sanctuary, a clone from Ramda that is more pure and integrates nicely with Fluture.
But if you need to start somewhere you can always check the community gitter chat and post questions. Reading the docs also helps a lot.
Hope it helps!
Not an expert, but since the experts aren't answering I thought I could Maybe help... ;)
The way I understand it, is that you use a Promise or Future to handle the async part of your data flow, and you use a Maybe or Either to handle weird/multiple/null-data.
E.g.: you can make your data transform function handle null like so:
const lengthDoubled = compose(x => x * 2, length);
const convertDataSafely = pipe(
Maybe,
map(lengthDoubled)
// any other steps
);
Then, in your Future, you can do something like:
Future(/* ... */)
.map(convertDataSafely)
.fork(console.error, console.log);
Which will either log a Nothing or a Just(...) containing an integer.
Full code sample: (npm install ramda, fluture and ramda-fantasy)
const Future = require('fluture');
const Maybe = require('ramda-fantasy').Maybe;
const { length, pipe, compose, map } = require("ramda");
// Some random transformation
// [] -> int -> int
const lengthDoubled = compose(x => x * 2, length);
const convertData = pipe(
Maybe,
map(lengthDoubled)
)
Future(asyncVal(null))
.map(convertData)
.fork(console.error, console.log); // logs Nothing()
Future(asyncVal([]))
.map(convertData)
.fork(console.error, console.log); // logs Just(0)
Future(asyncVal([1,2,3]))
.map(convertData)
.fork(console.error, console.log); // logs Just(6)
Future(asyncError("Something went wrong"))
.map(convertData)
.fork(console.error, console.log); // Error logs "Something went wrong"
// Utils for async data returning
function asyncVal(x) {
return (rej, res) => {
setTimeout(() => res(x), 200);
};
};
function asyncError(msg) {
return (rej, res) => {
setTimeout(() => rej(msg), 200)
};
};

How to "synchronize" Observables?

I have this piece of code:
ngOnInit(): void
{
this.categories = this.categoryService.getCategories();
var example = this.categories.flatMap((categor) => categor.map((categories) => {
var links = this.categoryService.countCategoryLinks(categories.id)
.subscribe(valeur => console.log(valeur));
return categories.id
}));
}
The result are two observables.
One consists in a list of categories.
The second one is the number of items for a particular categories.id.
My question is as follow:
How could I get all this information structured in a particular data structure?
I would like to store categories and the number of items per category in the same data structure to be able to show them up in my TS component.
I went step by step trying to fix my issues and I went to have the following code that is almost the solution:
this.categories = this.categoryService.getCategories();
var example = this.categories.mergeMap((categor) => categor.map((myCateg) =>
{
this.categoryService.countCategoryLinks(myCateg.id)
.map(numlinks => Object.assign(myCateg,{numLinks: numlinks}))
.subscribe(valeur => console.log(valeur));
return myCateg.id
}));
It gives the following output:
Where numLinks is still an object... (containing my count value) Any idea on how to transform it to a json property like categoryName or id??
Thanks in advance and Regards,
Here is the solution to the problem:
ngOnInit(): void
{
this.categories = this.categoryService.getCategories();
const example = this.categories
.mergeMap((categor) => categor
.map((myCateg) => {
this.categoryService.countCategoryLinks(myCateg.id)
.map(numlinks => {
myCateg.numlinks = numlinks.count;
return myCateg;
})
//Object.assign(myCateg,{numLinks: numlinks}))
.subscribe(value => console.log(value));
return myCateg
}));
example.subscribe(val => console.log("value2: "+val));
}
Once more, the solution comes from the mergeMap() operator. :-)
this.categoryService.getCategories()
.mergeMap(val => val) // "Flatten" the categories array
.mergeMap(category =>
this.categoryService.countCategoryLinks(category.id)
// Write the number of links on the `category` object
.map(numLinks => Object.assign(category, {numLinks: numLinks}))
)
.toArray()
.subscribe(allCategoriesWithNumLinks => console.log(allCategoriesWithNumLinks));
I'm not going into the specifics (the first mergeMap to flatten the array, Object.assign() to produce the final object) since it seems like we covered all that in a previous thread we had, but feel free to ask questions if anything is unclear.
Philippe's questions:
Why flatten the categories array? I'm assuming getCategories() emits a SINGLE array containing all the categories. Since you want to run an HTTP request for each category, it's more convenient to have an observable emitting each category individually. That's what the first mergeMap() does: it transforms Observable<Category[]> into Observable<Category>.
Why create an object? You said you wanted to store everything in the same data structure. That's what Object.assign does: it writes the number of links found for each category on the category object itself. This way, you end up with ONE object containing the information from TWO observables (category + numLinks).

Javascript/Ramda: How to make the following code functional

Hi I have the following object structure,
const usersList = {
NFr9F4WbBxR4H5ajolbS6q0skPF2: {
name: "justin davidson",
uid: "NFr9F4WbBxR4H5ajolbS6q0skPF2"
},
asas9F4WbBxR4H5ajolbS6q0sasF2: {
name: "sawyer davidson",
uid: "asas9F4WbBxR4H5ajolbS6q0sasF2"
}
}
It has a user ID as key, and it's user object nested within. I want to store the inner user data. I've been using Ramda JS and have done so by doing the following,
let x = []
const y = R.keys(usersList).forEach((uid) => {
x.push(usersList[uid])
return x
})
which returns
[{"name":"justin davidson","uid":"NFr9F4WbBxR4H5ajolbS6q0skPF2"},
{"name":"sawyer davidson","uid":"asas9F4WbBxR4H5ajolbS6q0sasF2"}]
..however I'd like achieve the same in a purely functional way. What would be the best approach here? I'm guessing compose and map but I can't seem to work it out. Looking for a little direction.
Thanks
Just use map instead of forEach:
const x = R.keys(usersList).map((uid) => usersList[uid])
It looks like there's also a values method that does what you want:
const x = R.values(usersList)
There isn't always a function tucked away in some lib that does exactly what you want it to do. Showing how to do things on your own demonstrates that you don't have to feel "stuck" when you're faced with a problem and you can't find a magical function to solve it for you. Once you learn the function exists, sure, go ahead and replace your home-brew solution with the built-in. But until then, don't be afraid to write code and move on.
// ovalues :: (Object k:v) -> [v]
const ovalues = o =>
Array.from(Object.keys(o), k => o[k])
const usersList = {
NFr9F4WbBxR4H5ajolbS6q0skPF2: {
name: "justin davidson",
uid: "NFr9F4WbBxR4H5ajolbS6q0skPF2"
},
asas9F4WbBxR4H5ajolbS6q0sasF2: {
name: "sawyer davidson",
uid: "asas9F4WbBxR4H5ajolbS6q0sasF2"
}
}
console.log(ovalues(usersList))
So yep, R.values does exist in the Rambda library, but next time don't be afraid to try to solve it on your own. You have a powerful brain, now use it ^_^

Categories