I have a hard time understanding something related to using a concat function inside a mergeMap operator.
The documentation for concat says that
You can pass either an array of Observables, or put them directly as arguments.
When I put the Observables directly as arguments, like in the following example, I correctly get 20 and 24 in the console.
of(4)
.pipe(
mergeMap(number => concat(of(5 * number), of(6 * number)))
)
.subscribe(value => console.log(value));
But when I put them as an array, then in the console I get the Observables and not their values:
of(4)
.pipe(
mergeMap(number => concat([of(5 * number), of(6 * number)]))
)
.subscribe(value => console.log(value));
Here's a live version in Stackblitz.
Any idea why is that? Shouldn't both examples work identically?
Those two scenarios are different and they should not work identically. concat takes Observables as arguments and it will sequentially subscribe to those streams and only subscribe to the next Observable when the previous one completed. Every operator or creation method returns an Observable. This means that in the first example, when you are using concat, it will return an Observable that emits 20 and then 24. Because you are dealing with a nested Observable you have to use mergeMap which will subscribe to the resulting Observable returned by concat.
Now in the second example, if you pass in an array, concat will convert this (using from() internally) to an Observable that emits 2 values, and those values are Observables again. So you have 3 levels of nesting here. The first is the most outer Observable, the source, which of(4), the second level is the one you map to inside your mergeMap and the third in the second example are the Observables inside your array. The thing is you only flatten the levels up to level 2 but not the 3rd level. Again, in your second example the Observable returned by mergeMap emits two Observables but those are just the proxies and not the values emitted by these Observables. If you want to subscribe to those as well, you could chain on another mergeMap like so
concatArray() {
of(4)
.pipe(
mergeMap(number => concat([of(5 * number), of(6 * number)])),
mergeMap(x => x)
)
.subscribe(value => console.log(value));
}
Another way is to spread the array so that concat does not receive an object that is ArrayLike but rather Observables directly:
concatArray() {
of(4)
.pipe(
mergeMap(number => concat(...[of(5 * number), of(6 * number)]))
)
.subscribe(value => console.log(value));
}
Both will print out:
20
24
I hope this makes this a little bit more clear and describes the differences between the first and the second example.
Related
In the following example, for whatever reason, initial value is ignored.
const frameRateSubject: BehaviorSubject<number> = new BehaviorSubject(24);
const loadedMetadata$: Observable<Event> = fromEvent(this.videoElement, 'loadedmetadata');
frameRateSubject.asObservable()
.pipe(
withLatestFrom(loadedMetadata$), // by commenting out this line, both 24 and 20 values are received
tap(([frameRate]: [number, Event]) => {
// initial value of 24 is never received, why is it?
console.log('frameRateSubject', frameRate)
})
)
.subscribe();
setTimeout(() => {
frameRateSubject.next(20)
}, 10000)
Any ideas why?
withLatestFrom combines the source observable (here frameRateSubject$) with other streams (loadedMetadata$) and emits values calculated from the latest values of each, only when the source emits.
But in your case loadedMetadata$ hasn't emitted when frameRateSubject$ emits 24. So the value is skipped.
CombineLatest is most likely the operator you are looking for here.
of([1,2,3]).subscribe(console.log)
prints:[1,2,3]
But:
of([1,2,3]).pipe(concatAll()).subscribe(console.log)
prints:
1
2
3
Why the above happens? Why adding concatAll() emits the elements of the array one by one? Isn't this somehow the opposite of what the word concat means?
I feel that concatAll() acts differently depending on the input.
Consider also this:
from([of(1),of(2),of(3)]).pipe(concatAll()).subscribe(console.log)
It will again print:
1
2
3
So of([1,2,3]).pipe(concatAll()) == from([of(1),of(2),of(3)]).pipe(concatAll())
But of([1,2,3]) != from([of(1),of(2),of(3)]) because subscribing to the latter will print:
Observable { _isScalar: false, _subscribe: [Function] }
Observable { _isScalar: false, _subscribe: [Function] }
Observable { _isScalar: false, _subscribe: [Function] }
The right side of the above equality is pretty clear to me, but where is documented that concatAll() should emit all the values of the array separately, acting like a pipeable from?
All the operators that deal with higher-order observable operators, perform the same conversion that RxJS's from operator does.
that is:
mergeMap(_ => ([1,2,3]))
Is basically the same as
mergeMap(_ => from([1,2,3]))
That works the same when merging/switching/concatenating promises and other iterables.
The difference you're seeing in behaviour is also attributed to the fact that from creates a new observable, while the higher-order observable operators tend to create a new observables and define behaviour around how those observables are subscribed to.
so concatAll is adding extra behaviour.
Finally, just because two things have similar outputs for a given example, doesn't mean they're doing the same thing under the hood.
of([1,2,3]).pipe(concatAll()) and from([of(1),of(2),of(3)]).pipe(concatAll()) describe two very different set of behaviours that in (this case) give you the same output to the console.
The first emits an array and has that array turned into a stream of numbers by concatAll(). The second emits three observables, and has has concatAll() subscribe to each only when the previous one completes. They complete after emitting just one number.
You can see the distinction clearly with your second example:
of([1,2,3]) and from([of(1),of(2),of(3)])
The first emits and array, the second emits three observables. That hasn't changed.
concat([1,2,3]), &&
concat(of(1), of(2), of(3))
Emit the same output again as well.
I have a list of observables I'd like to fire 5 at a time. I've tried using a mergeMap, but clearly I'm using it wrong:
// obsArray defined above... is a Array of Observables... about 30 of them
of(obsArray).pipe(
mergeMap(x => x, 5)
).subscribe();
The issue is that x in the mergeMap is the entire observable list. How do I send 5 at a time to be fired (they are http calls)?
Use from to emit single items from an array. You can also replace mergeMap(x => x) with mergeAll.
from(obsArray).pipe(mergeAll(5))
Given an array of observables where each observable can emit EMPTY, how can I execute the next observable in the array on the condition that the previous observable returned EMPTY or stop iterating the array and return a non EMPTY value once an observable in the array has emitted the first non EMPTY value?
Here would be one approach:
const emptyVal = Symbol('empty');
const src$ = concat(
...arrOfObservable.map(
obs$ => obs$.pipe(
last(null, emptyVal)
)
)
).pipe(
filter(v => v !== emptyVal),
first()
)
The last() operator will emit the last emitted value, after the source completes. If there is no last value(obs$ has used EMPTY), it will emit emptyVal.
filter(v => v !== emptyVal) will make sure that we keep iterating until we get a non empty value.
With first(), we'd stop the iteration and we'd get the emitted value.
Note: this approach works if the obs$ completes at some time, otherwise last won't be able to do its job.
In rxjs5 doc, it mentions 'To reduce polymorphism and get better performance out of operators, some operators have been split into more than one operator'. What does it actually mean and how to use the mergeMapTo operator?
From the docs, mergeMapTo:
It's like mergeMap, but maps each value always to the same inner Observable.
I see mergeMapTo as a shortcut to always output the same value. mergeMapTo doesn't care about the source value.
Also from the docs:
Maps each source value to the given Observable innerObservable
regardless of the source value, and then merges those resulting
Observables into one single Observable, which is the output
Observable.
You'll see that mergeMap takes a function while mergeMapTo takes a value:
An example with mergeMap (we're transforming values):
Rx.Observable.of(1, 2, 3).mergeMap(x =>
Rx.Observable.interval(1000).map(i => x+i)
);
While using mergeMapTo we can take values from a stream and always output the same value (also transforming, but always to the same value):
Rx.Observable.of(1, 2, 3).mergeMapTo(10);