Chaining two dependent observables in rxjs - rxjs

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.

Related

Is it possible to write an rxjs operator that controls subscription to it's source?

Let's say I have an observable source that has the following properties:
It makes a network request the first time it's subscribed to
It's idempotent, so it will always emit the same value after it's first subscribed to
It's low priority, so we don6t6 want to be too eager in subscribing to it
What would be ideal is if there were some sort of operator delaySubscriptionUntil that would delay subscription until some other observable s emits a value.
So for example:
const s = new Subject<void>();
const l = source
.pipe(
delaySubscriptionUntil(s));
l.subscribe(console.log);
// The above won't print anything until this line executes
s.next();
I looked through the documentation to see if there's an existing operator like this, but haven't found one.
You just put the subject first and switchMap
const l = s.pipe(
switchMap(() => source)
);
Once the subject emits then the source will be subscribed to.
Any thing that is after wont work as it relies on the previous observable emitting a value. You can have a filter in the chain that stops the previous observable's emission being emitted but there is nothing you can pass back up the chain to control outer subscriptions.
You could use a takeWhile
let allow = false;
const l = source.pipe(
takeWhile(allow)
);
but here the subscription to source is active, it is emitting values, they are just stopped from being passed through.
So you could make a similar operator that keeps an internal flag and is flipped by a subject but source is still going to be emitting, you are just filtering values. You could buffer up the values if you don't want to lose them.
You could use share() which will share the result of anything that happened before it until you call sub.next() with a new url then the request will happen again.
const sub = new BehaviorSubject<string>('http://example.com/api');
const result$ = sub.pipe(
exhaustMap(url => this.http.get(url)),
share()
)
// Each one of these subscriptions will share the result.
// The http request will be called only once
// until you send a new url to the BehaviorSubject.
result$.subscribe(val => console.log(val));
result$.subscribe(val => console.log(val));
result$.subscribe(val => console.log(val));
result$.subscribe(val => console.log(val));

Altering combineLatest to emit the result without awaiting other observables

I have this rxjs code where cobineLatest is used
cobineLatest(
this.controls.toArray().map(c => c.changeEvent.asObservable())
).subscribe(x => {
console.log(x);
});
The thing is that there won't be any result in subscription until all observables emits, I wonder how would you change that behavior so it will start emitting even if one single observable emits?
I suggest you to just pipe the single observables to start with null. This way you ensure that each observable has emitted at least one value:
cobineLatest(
this.controls.toArray().map(c => c.changeEvent.asObservable().pipe(startWith(null)))
).subscribe(x => {
console.log(x);
});
If your are interested in the emitted value only (not the array), then merge might be your friend.
merge(
this.controls.toArray().map(c => c.changeEvent.asObservable())
).subscribe(x => {
console.log(x); // x is not an array
});

RxJS: merge with single emit on completion

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>

Replace Observable which already has subscribers with other Observable

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

rxjs concatMap with array

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(),
);

Resources