Reputation: 1122
I am not Typescript, Javascript or frontend developer at all. I have inherited Angular 13 project. There is a function returning Observable<Dto[]>
. I see something along this lines in the code:
this.service.getStuff(arg).pipe(
mergeMap(x => x),
concatMap(y => { return this.service.getOtherStuff(x.subObj.id).pipe(
map(v => ({ obj1: y, obj2: v})));
})
).subscribe(
// ... do something with results
I understand what it does and why it does that, but I can't for the life of me understand why would mergeMap
turn an array into a stream of separate values. Documentation says only that mergeMap
:
Projects each source value to an Observable which is merged in the output Observable
My understanding is that source value
in this case is Dto[]
, so projection should be Observable<Dto[]>
, not a list of Observables created from separate values in the array.
I suspected that maybe this maneuver is done later, but putting tap
with console.log
around mergeMap
shows that input is indeed an array and output is a stream objects.
So what is happening and why? And why isn't it explained in the documentation while we're at it.
Upvotes: 2
Views: 941
Reputation: 1122
I have accepted @martin's answer as it allowed me to find an answer in the documentation - but not on https://rxjs.dev site but rather on https://reactivex.io site. As @martin pointed out, what project
function should return is ObservableInput
, which is defined as:
type ObservableInput<T> = Observable<T> | InteropObservable<T> | AsyncIterable<T> | PromiseLike<T> | ArrayLike<T> | Iterable<T> | ReadableStreamLike<T>;
Part ArrayLike<T>
is the key and on reactivex.io we can read more (and I couldn't find this VERY important piece of info on https://rxjs.dev site), most importantly:
Array Arrays can be interpreted as observables that emit all values in array one by one, from left to right, and then complete immediately.
Array-like Arrays passed to operators do not have to be built-in JavaScript Arrays. They can be also, for example, arguments property available inside every function, DOM NodeList, or, actually, any object that has length property (which is a number) and stores values under non-negative (zero and up) integers.
This piece of information is of paramount importance and I can't believe it is not written in the docs on the other site.
Upvotes: 1
Reputation: 96891
This is intended behavior. If you look at the documnetation it says that the project
function needs to return ObservableInput
and not just Observable
. In fact, all RxJS operators that expect an Observable as a parameter will work with any ObservableInput
.
ObservableInput
can be for example an Observable (obviously), async generator, an array or a Promise.
So using mergeMap(x => x)
is really only used to unwrap the array and is equivalent to mergeMap(x => from(x))
. Maybe more clean solution would be using just mergeAll()
operator that is intended to be used with higher-order Observables.
Upvotes: 5
Reputation: 23825
The issue here is that you are using mergeMap
not how it is normally intended. The map operators like mergeMap
or switchMap
expect an Observable (or a Promise) as the return value. But you are just returning the plain array. It seems like mergeMap
sees that this is not an Observable and wraps it in a from
operator. This creates an Observable from the array where each element emits individually.
If you do not want this behavior, wrap your value in of
.
mergeMap(x => of(x))
But really this mergeMap
does not have any effect either way. Why are you using this do-nothing function here? If you don't plan to return an Observable, why not use a map
?
Upvotes: 2