Replace Observable which already has subscribers with other Observable - rxjs

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

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

Repeat on an BehaviorSubject

I want to reemit the last value of my observable at a fix interval, to I tried
obs.pipe(repeat({delay:1000})).subscribe(x => console.log('Emitted', x));
but it did not work. after looking into this, my observable is in fact a BehaviorSubject.
So my Question is Why does the 1st emits every second
of('Observable').pipe(repeat({ delay: 1000 })).subscribe(x => console.log(x));
but not the this?
var bs = new BehaviorSubject('BehaviorSubject');
bs.pipe(repeat({ delay: 1000 })).subscribe(x => console.log(x));
How to do it with my BehaviorSubject?
Edit
And I would also like to reset my timer when the subject emits a new value.
the solution I found is
var bs = new BehaviorSubject('BehaviorSubject');
bs.pipe(switchMap(x => timer(0,1000).pipe(map => () => x)).subscribe(x => console.log(x));
but it feels ugly.
You can derive an observable from your BehaviorSubject that switchMaps to a timer that emits the received value. Whenever the subject emits, the timer is reset and will emit the latest value:
const bs = new BehaviorSubject('initial value');
const repeated = bs.pipe(
switchMap(val => timer(0, 1000).pipe(
map(() => val)
))
);
Here's a StackBlitz demo.
So my Question is Why does the 1st emits every second, but not the this?
The reason your example code using of as the source works and not the code using the BehaviorSubject can be found in the documentation of the repeat operator:
Returns an Observable that will resubscribe to the source stream when the source stream completes.
The observable created using of completes after it emits the provided value, so it will resubscribe. Since the BehaviorSubject was not completed, it will not resubscribe.

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
});

Chaining two dependent observables in 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.

RxJS finalize operator with shareReplay called for each subscriber

I have the following code:
const source = interval(1000).pipe(
take(3),
finalize(() => console.log('complete')),
shareReplay({ bufferSize: 1, refCount: true}),
);
source.subscribe(x => console.log(x));
source.subscribe(x => console.log(x));
source.subscribe(x => console.log(x));
Now, the complete log called once. When I move the finalize operator to be after the shareReplay, it called three times, which I guess it's per subscriber, but why it works like that?
const source = interval(1000).pipe(
take(3),
shareReplay({ bufferSize: 1, refCount: true}),
finalize(() => console.log('complete')),
);
The finalize operator subscribed to the internal ReplaySubject which complete once, no?
Finalize operator emits when its source observable is complete.
ShareReplay operator creates a new observable for each subscriber, and replay original observable values.
Take operator only completes after finishing to emit the specified number of take times.
So in your first case, Take(3) observable will emit 3 times, then it’s observable is complete and finalized is called once.
In your second case, ReplayShare will create a new observable per subscriber (over all 3 subscriptions) and finalized will be called 3 times, once for each created observable.

Resources