Having getLastOrder() and getUserDetails(lastOrder.owner) async operations. (both returns Observable)
Looking for fancy operator to get combined result that will include both: [lastOrder, userDetails] who owns that order.
P.S. doing that via temporary variables does not look pretty
Not sure about fancy operators, but this can be solved quite easily by combining the operators flatMap and map. Also note that my functions just return dummy data but should work the same way with any observables:
function getLastOrder() {
return Rx.Observable.of({owner: 'foo'})
}
function getUserDetails(owner) {
return Rx.Observable.of({details: 'bar', owner})
}
function combined() {
return getLastOrder()
.flatMap(order => getUserDetails(order.owner).map(details => [order, details]))
}
When you think about prettyness, also think about the next guy who will read it.
Related
Is using a variable from outside an observable within an operator considered a (significantly) bad practice?
createObservableExample1(parameter1: string, obs$: Observable<string>): Observable<string> {
return obs$.pipe(
map( x => {
const returnValue = `${parameter1}, ${x}`;
return returnValue;
})
);
}
I understand you can do something like this:
createObservableExample2(parameter1: string, obs$: Observable<string>): Observable<string> {
return combineLatest([
of(parameter1),
obs$
]).pipe(
map( (x, y) => {
const returnValue = `${x}, ${y}`;
return returnValue;
})
);
}
But is it worth it?
Does this just come down to accessing variables from outside the scope of anonymous function? Would this force the context of the enclosing method to exist for longer than it should? I remember a code tool I used to use for C# complaining about something similar to this. I have found somewhat related topics by searching for, "anonymous functions and closures", but as of yet, nothing really discussing the scenario explained above.
I ask because I have been creating some relatively complex observables that have enormous operator chains, and constantly adding the needed variables, using combineLatest and of, from the parent scope can make the code even harder to follow.
When I teach Reactive programming to neophytes, I try to make them grasp : Do not break the reactivity by having uneccessary side effects :
no input that from a state (for example using a class or instance property
no storing outside value.
There is none of these red flags in your example. Your function is pure & idempotent with both implementation, go with what ever you like and if possible be consistant within your code base !
Trying to learn RxJs, and I found what looks like a nice tutorial on that topic at https://www.learnrxjs.io.
I'm going through their primer section, and I am not clear on what the return statement in the pipe() function actually does or what it means. These are a couple of screen shots from their tutorial:
In traditional programming, I've always understood a return statement to be an exit - if function A calls function B, and function B has the line return 1, then control goes back to function A.
Is that what happens here? If so, in either of these two examples, where am I returning to???
Or what if I don't want to return anywhere but act on the data immediately? For example, in the error handling example, instead of return makeRequest..., I want to do something like const result = makeRequest.... Can I do that?
In general I'm having some conceptual difficulties around all the returns I've seen used with observables, and any help in explaining what they do/are would be appreciated. So would any other tutorial sites on RxJs.
These are all very similar constructs in javascript
function adder0(a,b){
return a + b
}
const adder1 = (a,b) => {
return a + b
}
const adder2 = (a,b) => a + b
console.log(adder0(3,7)) // 10
console.log(adder1(3,7)) // 10
console.log(adder2(3,7)) // 10
lets re-write the code from that example but with explicit function definitions (instead of arrow syntax).
function maker(value){
return makeRequest(value).pipe(
catchError(handleError)
);
}
function actioner(value){
// take action
}
source.pipe(
mergeMap(maker)
).subscribe(actioner)
The thing to notice is that you never actually call these functions. RxJS does that for you. You give them a function and they'll call it when they're good and ready based on the spec for a given operator.
Q: can RxJs operators be used to flatten an array, transform items, then unflatten it, whilst maintaining a continuous stream (not completing)?
For the simplified example here: https://stackblitz.com/edit/rxjs-a1791p?file=index.ts
If following the approach:
mergeMap(next => next),
switchMap(next => of(***transforming logic***)),
toArray()
then the observable does not complete, and the values do not come through. A take(1) could be added but this is intended to be a continuous stream.
If using:
mergeMap(next => next),
switchMap(next => of(***transforming logic***)),
scan()
then this works great. However, then each time the source observable emits, the accumulator never resets, so the scan() which is intended to accumulate the values back into an array ends up combining multiple arrays from each pass. Can the accumulator be reset?
Obviously it can be accomplished with:
switchMap(next => of(next.map(***transforming logic***)))
But my real-world example is an awful lot more complicated than this, and is tied into NgRx.
Here would be one approach:
src$.pipe(
mergeMap(
arr => from(arr)
.pipe(
switchMap(item => /* ... */),
toArray(),
)
)
)
For each emitted array, mergeMap will create an inner observable(from(..)). There, from(array) will emit each item separately, allowing you to perform some logic in switchMap. Attaching toArray() at the end will give you an array with the results from switchMap's inner observable.
You don't need to use mergeMap or switchMap here. You would only need those if you are doing something asynchronously. Like if you were taking the input value and creating an observable (ex: to make an http call).
By using of inside of mergeMap, you are essentially starting with an Observable, taking the unpacked value (an array), then turning it back into an Observable.
From your stack blitz:
The reason your first strategy doesn't complete is because toArray() is happening on the level of the source (clicksFromToArrayButton), and that is never going to complete.
If you really wanted to, you could nest it up a level, so that toArray() happens on the level of your array (created with from(), which will complete after all values are emitted).
const transformedMaleNames = maleNames.pipe(
mergeMap(next => from(next).pipe(
map(next => {
const parts = next.name.split(' ');
return { firstName: parts[0], lastName: parts[1] };
}),
toArray()
)
),
);
But... we don't really need to use from to create an observable, just so it can complete, just so toArray() can put it back together for you. We can use the regular map operator instead of mergeMap, along with Array.map():
const transformedMaleNames = maleNames.pipe(
map(nextArray => {
return nextArray.map(next => {
const parts = next.name.split(' ');
return { firstName: parts[0], lastName: parts[1] };
})
})
);
this works, but isn't necessarily utilizing RxJS operators fully?
Well, ya gotta use the right tool for the right job! In this case, you are simply transforming array elements, so Array.map() is perfect for this.
But my real-world example is an awful lot more complicated than this
If you are concerned about the code getting messy, you can just break the transformation logic out into it's own function:
const transformedMaleNames = maleNames.pipe(
map(next => next.map(transformName))
);
function transformName(next) {
const parts = next.name.split(' ');
return { firstName: parts[0], lastName: parts[1] };
}
Here's a working StackBlitz.
I'm a bit confused by the various definitions of an operator in rxjs.
Below I provide some of the definitions:
1 A Pipeable Operator is a function that takes an Observable as its input and returns another Observable.
Creation Operators are the other kind of operator, which can be called as standalone functions to create a new Observable
2 An operator is a function that takes one observable (the source) as its first argument and returns another observable (the destination, or outer observable)
3 Operators take configuration options, and they return a function that takes a source observable.
4 Operators should always return an Observable [..] If you create a method that returns something other than an Observable, it's not an operator, and that's fine.
Since 1,2,4 seem to be conflicting with 3, which definition is the correct one. Is there a better definition of rxjs operators?
For example: in case of map. Is map() itself the operator? Or the operator is the return value of map()?
Is map() itself the operator? Or the operator is the return value of map()?
Current implementation of the map() looks like this:
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return function mapOperation(source: Observable<T>): Observable<R> {
if (typeof project !== 'function') {
throw new TypeError('argument is not a function. Are you looking for `mapTo()`?');
}
return source.lift(new MapOperator(project, thisArg));
};
}
So, map() is a function. It is an operator in RxJS terms, yes, but it's still a regular JavaScript function. That's it.
This operator receives projection callback function which gets called by the map operator. This callback is something you're passing to map(), e.g. value => value.id from this example:
source$.pipe(map(value => value.id))
The return value of the map is also a function (declared as OperatorFunction<T, R>). You can tell that it is a function since map returns function mapOperation().
Now, mapOperation function receives only one parameter: source which is of type Observable<T> and returns another (transformed) Observable<R>.
To summarize, when you say:
A Pipeable Operator is a function that takes an Observable as its input and returns another Observable.
This means that an RxJS operator (which is a function) is pipeable when it takes an Observable as its input and returns another Observable which in our case is true: a map operator indeed returns a function (mapOperation) whose signature ((source: Observable<T>): Observable<R>) indicates exactly that: it takes one Observable and returns another.
An operator is a function that takes one observable (the source) as its first argument and returns another observable (the destination, or outer observable)
I already mentioned couple of times that an operator is a just function.
Operators take configuration options, and they return a function that takes a source observable.
Yes, in this case, a map() could be called operator since it receives configuration option - a projecttion callback function. So, there's really no conflicts here since many operators are configurable.
I'd say that there's conflict in this one:
Operators should always return an Observable [..] If you create a method that returns something other than an Observable, it's not an operator, and that's fine.
I guess that this is an old definition when pipeable operators weren't pipeable. Before pipeable operators were introduced (I think in version 5 of RxJS), operators were returning Observables. An old map() implementation indicates just that.
For more information about why creators of RxJS decided to introduce pipeable operators, please take a look at this document.
Another great article about what are Observables can be found here.
Also:
Creation Operators are the other kind of operator, which can be called as standalone functions to create a new Observable.
of() is an example of creation operator which returns (creates) an Observable. Please take a look at the source code.
TL;DR: A map() is a function that usually has one parameter (a projection callback function) which also returns a function that receives a source Observable and returns a destination Observable.
EDIT: To answer your question from comments, I'd like to do it here.
Yes, in RxJS 6 you can create a function that accepts observable and returns another one and that would be the operator. E.g.
function myOperatorFunction(s: Observable<any>) {
return of(typeof s);
}
and you'd call it like
source$.pipe(myOperatorFunction);
Please notice that I didn't call myOperatorFunction in pipe(), I just passed the reference to it, i.e. I didn't write myOperatorFunction with parenthesis, but without them. That is because pipe receives functions.
In cases where you need to pass some data or callback functions, like in map example, you'd have to have another function that would receive your parameters, just like map receives projection parameter, and use it however you like.
Now, you may wonder why there are operators that don't receive any data, but are still created as functions that return function, like refCount(). That is to coincide with other operators that mostly have some parameters so you don't have to remember which ones don't receive parameters or which ones have default parameters (like min()). In case of refCount, if it was written a bit different than it is now, you could write
source$.pipe(refCountOperatorFunction);
instead of
source$.pipe(refCount());
but you'd have to know that you have to write it this way, so that is the reason why functions are used to return functions (that receives observable and returns another observable).
EDIT 2: Now that you know that built in operators return functions, you could call them by passing in source observable. E.g.
map(value => value.toString())(of())
But this is ugly and not recommended way of piping operators, though it would still work. Let's see it in action:
of(1, 2, true, false, {a: 'b'})
.pipe(
map(value => value.toString()),
filter(value => value.endsWith('e'))
).subscribe(value => console.log(value));
can be also written like this:
filter((value: string) => value.endsWith('e'))(map(value => value.toString())(of(1, 2, true, false, {a: 'b'})))
.subscribe(a => console.log(a));
Although this is a completely valid RxJS code, there's no way you can think of what it does when you read the latter example. What pipe actually does here is that it reduces over all the functions that were passed in and calls them by passing the previous source Observable to the current function.
Yes. map itself is an operator.
Every operator returns an Observable so later on you can subscribe to the Observable you created.
Of course when I say 'you created' I mean that you created via a creation operator or using the Observable class: new Observable
A pipeable operator is just an operator that would be inside the pipe utility function.
Any function that returns a function with a signature of Observable can be piped and that's why you can create your own Observables and pipe operators to it.
I am pretty much sure that you already know all of this and all you want to know is why there is a conflict in what you are reading.
First, you don't have to pipe anything to your Observable.
You can create an Observable and subscribe to it and that's it.
Since you do not have to pipe anything to your Observable, what should we do with the operators?
Are they used only within a pipe?
Answer is NO
As mentioned, an operator takes configuration options, in your example:
const squareValues = map((val: number) => val * val);
and then it returns a new function that takes the source Observable so you can later on subscribe to it:
const squaredNums = squareValues(nums);
As we can see, map took (val: number) => val * val and returned a function that now gets the Observable nums: const nums = of(1, 2, 3);
Behind the scenes map will take the source observable and transform according to the configuration.
Now the important thing:
Instead of doing that in this way (which you can but no need for that), it is better to pipe your operators so you can combine all of the functions (assuming you are using more operators) into one single function.
So again, behind the scenes you can refer to pipe as a cooler way to make operations on your source Observable rather then declaring many variables that uses different operators with some configuration which return a function that takes a source Observable.
I have an array of observables which I'm executing in parallel using:
let observables: Observable<any>[]
Observable.forkJoin(observables)
This works perfectly, however, I need to execute the array of observables sequentially, and only emit one result if the last observable has been completed. That's when I tried to use
Observable.concat(observables)
But this returns multiple results, and not only one - combined - result which I get when using forkJoin. So I actually need a combination of the two.
I've tried to use the reduce functionality to execute them sequentially, like this:
return observables.reduce((previous, current) => {
return previous.flatMap(() => current);
}, Observable.empty());
But with this solution the observables are not executed at all.
Assuming that your observables emit singular values, not arrays, you could rework your current approach to something like:
return Observable.concat(...observables).reduce((acc, current) => [...acc, current], []);
or even shorter:
return Observable.concat(...observables).toArray();
In the case that they emit array values, you could do the following:
const source = Observable.concat(...observables).flatMap(list => list).toArray();
As Jota.Toledo or Mateusz Witkowski showed in their answers, with the new syntax of RxJS you can do:
return concat(...observables).pipe(toArray());
You can you use toArray() operator:
Observable.concat(observables).toArray().subscribe()
As stated in RxJS documentation: it creates "an observable sequence containing a single element with a list containing all the elements of the source sequence".