There is Higher Order Observable observable
const obs1 = interval(1000).pipe(take(5));
const obs2 = interval(500).pipe(take(2));
const obs3 = interval(2000).pipe(take(1));
//emit three observables
const source = of(obs1, obs2, obs3);
How to use concatMap in order to as soon as the previous observable is completed, the next one immediately begins. After it will return result array of Observable.Need to use concatMap.
You'll need to use toArray() to collect all emissions from all source Observables.
source.pipe(
concatMap(observable => observable),
toArray(),
);
Related
I have an observable that emits a series of messages, say obs1. Then a second observable, obs2, that needs some data from the last message emitted by obs1 and emits another series of messages. I would like to "chain" these 2 observables to produce an observable obs3 that serially emits ALL messages from obs1 and obs2.
The solution I came up with so far is:
obs3 = concat(
obs1,
obs1.pipe(
last(),
concatMap(lastMessage => obs2(lastMessage)),
);
But this has the flaw that obs1 is executed (subscribed to) 2 times.
Is there a more direct way to achieve this? Something like a concatMapWithSelf() operator that would work like this:
obs3 = obs1.pipe(
concatMapWithSelf(lastMessage => obs2(lastMessage)),
);
Thank you!
Sounds like you could use ConnectableObservable. In RxJS 7 I believe it would be even easier and better readable with multicast() but that's going to be deprecated in RxJS 8 so the only option is probably wrapping the source Observable with connectable() and then manually calling connect().
const obs1 = connectable(
defer(() => {
console.log('new subscription');
return of('v1', 'v2', 'v3', 'v4');
})
);
const obs2 = msg => of(msg);
const obs3 = merge(
obs1,
obs1.pipe(
last(),
concatMap(lastMessage => obs2(lastMessage))
)
);
obs3.subscribe(console.log);
obs1.connect();
Live demo: https://stackblitz.com/edit/rxjs-2uheg4?devtoolsheight=60
If obs1 is always asynchronous then probably you could use share() but that would behave differently with synchronous sources so using ConnectableObservable should be more safe.
Is there an RxJS factory function (or more generally a pattern) to merge several others together, but only emit once when all of them have completed?
The use case I want this for, is to wait for several parallel operations, and then do something else, once they complete. For promises this can be done like this:
Promise.all(A, B, C).then(() => console.log('done'));
For observables, the best I've come up with yet is
merge(A, B, C).pipe(takeLatest(1)).subscribe(() => console.log('done'));
This doesn't account for A, B, and C being empty, though. And it doesn't provide a deterministic value to the subscriber. Isn't there a simple built-in solution for this use-case?
You can use forkJoin. This operator emits once all its given observables are completed.
const { Observable, of, forkJoin } = rxjs;
const obs1$ = new Observable(subscriber => {
subscriber.next('obs1$ - value 1');
subscriber.next('obs1$ - value 2');
subscriber.complete();
})
const obs2$ = of('obs2$');
const obs3$ = of('obs3$');
const result$ = forkJoin(
obs1$,
obs2$,
obs3$
);
result$.subscribe(v => console.log(v));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>
What is the difference between merge and mergeAll? They both seem identical to me:
http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-mergeAll
http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#static-method-merge
merge is a static creation method that flattens group of observable.
according to the docs
Flattens multiple Observables together by blending their values into one Observable.
simply it will take a group of observables, and flattens them within one, so whenever any observable emits a value, the output will emit a value.
mergeAll However is different, it is an instance method that works with higher order observables (an observable that emits observables), according to docs
Converts a higher-order Observable into a first-order Observable which concurrently delivers all values that are emitted on the inner Observables.
I think that sums it up, but mergeAll can be confusing, so let's look at this example provided by rxjs docs
import { fromEvent, interval } from 'rxjs';
import { take, map, mergeAll } from 'rxjs/operators';
const higherOrder = fromEvent(document, 'click').pipe(
map((ev) => interval(1000).pipe(take(10))),
);
const firstOrder = higherOrder.pipe(mergeAll(2));
firstOrder.subscribe(x => console.log(x));
you have a document click observable (higher order) which return an interval observable (inner observable) that emits a value every second, it will complete after 10 intervals emits, which means every time you click on the document, a new interval will be returned, here where merge all comes in, it will subscribe to these intervals returned by the higher order observable, and flattens them into one observable, the first order observable, the argument 2, is to limit to 2 concurrent intervals at a time, so if you clicked 3 times, only 2 will run, but since these 2 intervals will complete after 10 seconds, then you can click again and mergeAll will subscribe to the new intervals.
Both merge and mergeAll inherit from mergeMap !
mergeAll
mergeAll is the same as calling mergeMap with an identity function(const identity = x => x)
mergeAll() === mergeMap(obs$ => obs$)
Example:
of(a$, b$, c$)
.pipe(
mergeAll(),
)
.subscribe()
// Same as
of(a$, b$, c$)
.pipe(
mergeMap(obs$ => obs$)
)
.subscribe()
Both will subscribe to the incoming observables(a$, b$ and c$) and will pass along to their values to the data consumer. Thus, a$, b$ and c$ are considered inner observables.
merge
Armed with the knowledge from the previous section, understanding merge should not be difficult.
merge(a$, b$, c$).subscribe() is essentially the same as
const observables = [a$, b$, c$];
new Observable(subscriber => {
for (let i = 0; i < observables.length; i++) {
subscriber.next(observables[i]);
}
subscriber.complete();
}).pipe(
mergeAll()
).subscribe();
I have a stream of values in this form:
[1,2,3,1,2,1,1,1,2...]
I would like to convert it to a stream of groups in the form:
[[1,2,3],[1,2],[1],[1],[1,2]...]
New group should be created every time value becomes 1.
You can use the bufferWhen() operator to collect emitted values until an observable emits a value. In this example I use a subject to emit a shallow copy of the stream to the buffer.
The buffer will emit when ever the number 1 is emitted. If the stream starts with 1 then an empty array is emitted. So I filter that away.
const {from, Subject} = rxjs;
const {filter, bufferWhen, tap, skip} = rxjs.operators;
const stream = from([1,2,3,1,2,1,1,1,2]);
const trigger = new Subject();
stream.pipe(
tap(v => trigger.next(v)),
bufferWhen(() => trigger.pipe(filter(v => v === 1))),
filter(v => v.length)
).subscribe(x => console.log(x));
<script src="https://unpkg.com/#reactivex/rxjs#6.x/dist/global/rxjs.umd.js"></script>
You can use the scan() if you wanted to emit the trigger when the value decreases from the previous. Which is maybe a little better in logic, but using 1 as the trigger fits with the question.
For the record, I'm posting a solution alternative to the accepted answer.
NOTE: For finite streams concat(of(1)) is required before scan(), for distinctUntilChanged to emit the last observable.
const {of, from, ReplaySubject} = rxjs;
const {map, concat, scan, distinctUntilChanged, concatAll, toArray, delay} = rxjs.operators;
//considering infinite stream
const stream = from([1,2,3,1,2,1,1,1,2]);
stream.pipe(
scan( (acc, val) => {
if(val===1){
acc.complete()
acc=new ReplaySubject();
}
acc.next(val);
return acc;
}, new ReplaySubject()),
distinctUntilChanged(),
map(toArray()),
concatAll() );
It would be nice to collect some feedback regarding preferred solution.
I have an Observable which gets later gets "replace" with another Observable.
How can I swap the Observable without loosing my subscribers?
const source = NEVER
const source2 = interval(1000);
source.subscribe(x => console.log(x));
// source.switch(source2)
source.switch(source2) is obviously not a valid operation. But it demonstrates, what I'dlike to achieve.
Same example on StackBlitz:
https://stackblitz.com/edit/rxjs-76a7ew
What would I need to do after the subscribtion, so this code will start printing the numbers from interval?
so you want switch to source2
source.pipe(
switchMap(() => source2)
).subscribe(x => console.log(x)); // x here is source2
you could use mergeMap or concatMap as well but I would recommend to use switchMap in this case as it's going to cancel the previous emit