How to cleanly reconnect to a ReplaySubject while avoiding past memoization side-effects? - javascript

I hold state in one ReplaySubject that replays the last copy of the state. From that state, other ReplaySubjects are derived to hold...well, derived state. Each replay subject need only hold it's last calculated state/derived state. (We don't use BehaviorSubjects because they always give a value, but we only want a value derived from our parent observables.) It is always necessary to replay the value to new subscribers if we have already generated derived state.
I have a custom observable operator that accomplishes this in just the way I want it to, but it doesn't feel that clean. I feel like there should be an efficient way to accomplish this with RxJ's operators themselves.
I have tried the two most obvious approaches, but there are slight problems with each. The problem involves unsubscribing and re-subscribing.
Open the fiddle below, open your console, and click run. I will describe the problem with each output.
https://jsfiddle.net/gfe1nryp/1/
The problem with a refCounted ReplaySubject
=== RefCounted Observable ===
Work
Subscription 1: 1
Work
Subscription 1: 2
Work
Subscription 1: 3
Unsubscribe
Resubscribe
Subscription 2: 3
Work
Subscription 2: 6
Work
Subscription 2: 7
Work
Subscription 2: 8
This works well, the intermediate functions don't do any work when there is nothing subscribed. However, once we resubscribe. We can see that Subscription 2 replays the last state before unsubscribe, and then plays the derived state based on the current value in the base$ state. This is not ideal.
The problem with connected ReplaySubject
=== Hot Observable ===
Work
Subscription 1: 1
Work
Subscription 1: 2
Work
Subscription 1: 3
Unsubscribe
Work
Work
Work
Resubscribe
Subscription 2: 6
Work
Subscription 2: 7
Work
Subscription 2: 8
This one does not have the same problem as the refCounted observable, there is no unnecessary replay of the last state before the unsubscription. However, since the observable is now hot, the tradeoff is that we always do work whenever a new value comes in the base$ state, even though the value is not used by any subscriptions.
Finally, we have the custom operator:
=== Custom Observable ===
Work
Subscription 1: 1
Work
Subscription 1: 2
Work
Subscription 1: 3
Unsubscribe
Resubscribe
Work
Subscription 2: 6
Work
Subscription 2: 7
Work
Subscription 2: 8
Ahh, the best of both worlds. Not only does it not unnecessarily replay the last value before unsubscription, but it also does not unnecessarily do any work when there is no subscription.
This is accomplished by manually creating a combination of RefCount and ReplaySubject. We keep track of each subscriber, and when it hits 0, we flush the replay value. The code for it is here (and in the fiddle, of course):
Rx.Observable.prototype.selectiveReplay = function() {
let subscribers = [];
let innerSubscription;
let storage = null;
return Rx.Observable.create(observer => {
if (subscribers.length > 0) {
observer.next(storage);
}
subscribers.push(observer);
if (!innerSubscription) {
innerSubscription = this.subscribe(val => {
storage = val;
subscribers.forEach(subscriber => subscriber.next(val))
});
}
return () => {
subscribers = subscribers.filter(subscriber => subscriber !== observer);
if (subscribers.length === 0) {
storage = null;
innerSubscription.unsubscribe();
innerSubscription = null;
}
};
});
};
So, this custom observable already works. But, can this be done with only RxJS operators? Keep in mind, potentially there could be more than a couple of these subjects linked together like this. In the example, I'm only using one linking to the base$ to illustrate the issue with both vanilla approaches I've tried at the most basic level.
Basically, if you can use only RxJS operators, and get the output to match the output for === Custom Observable === above. That's what I'm looking for. Thanks!

You should be able to use multicast with a subject factory instead of a subject. Cf. https://jsfiddle.net/pto7ngov/1/
(function(){
console.log('=== RefCounted Observable ===');
var base$ = new Rx.ReplaySubject(1);
var listen$ = base$.map(work).multicast(()=> new Rx.ReplaySubject(1)).refCount();
var subscription1 = listen$.subscribe(x => console.log('Subscription 1: ' + x));
base$.next(1);
base$.next(2);
base$.next(3);
console.log('Unsubscribe');
subscription1.unsubscribe();
base$.next(4);
base$.next(5);
base$.next(6);
console.log('Resubscribe');
var subscription2 = listen$.subscribe(x => console.log('Subscription 2: ' + x));
base$.next(7);
base$.next(8);
})();
This overload of the multicast operator serves exactly your use case. Every time the observable returned by the multicast operator completes and is reconnected to, it creates a new subject using the provided factory. It is not very well documented though, but it basically replicates an existing API from Rxjs v4.
In case I misunderstood or that does not work let me know,

Related

Struggling with flatMap vs concatMap in rxJs

I am struggling to understand the difference between the flatMap and concatMap in rxJs.
The most clear answer that I could understand was that here difference-between-concatmap-and-flatmap
So I went and tried things out by my self.
import "./styles.css";
import { switchMap, flatMap, concatMap } from "rxjs/operators";
import { fromFetch } from "rxjs/fetch";
import { Observable } from "rxjs";
function createObs1() {
return new Observable<number>((subscriber) => {
setTimeout(() => {
subscriber.next(1);
subscriber.complete();
}, 900);
});
}
function createObs2() {
return new Observable<number>((subscriber) => {
setTimeout(() => {
subscriber.next(2);
//subscriber.next(22);
//subscriber.next(222);
subscriber.complete();
}, 800);
});
}
function createObs3() {
return new Observable<number>((subscriber) => {
setTimeout(() => {
subscriber.next(3);
//subscriber.next(33);
//subscriber.next(333);
subscriber.complete();
}, 700);
});
}
function createObs4() {
return new Observable<number>((subscriber) => {
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 600);
});
}
function createObs5() {
return new Observable<number>((subscriber) => {
setTimeout(() => {
subscriber.next(5);
subscriber.complete();
}, 500);
});
}
createObs1()
.pipe(
flatMap((resp) => {
console.log(resp);
return createObs2();
}),
flatMap((resp) => {
console.log(resp);
return createObs3();
}),
flatMap((resp) => {
console.log(resp);
return createObs4();
}),
flatMap((resp) => {
console.log(resp);
return createObs5();
})
)
.subscribe((resp) => console.log(resp));
console.log("hellooo");
I have used that playground here playground example
Questions
1)
From my understanding the use of flatMap should mix the outputs so that the console logs are like (1,3,2,4,5). I have tried more than 30 times and always come on the same row (1, 2, 3, 4, 5)
What am I doing wrong or have undestood wrong?
2)
If on createObs2() and createObs3() you remove the comments and include the code with multiple emitted events then things get messy. Even if you change to concatMap it messes things and results come mixed. Multiple numbers that I expect only once come multiple times. The result can be (1, 2, 33, 3, 2, 22, 3, 33, 4, 5, 4, 3, 4, 5) Why this happens?
How I test the example on playground. I just remove only 1 letter from the last console.log("hello"). Only one change for example console.log("heloo") and is then observed and project is compiled again and output printed in console.
Edit: The reason I have gone to flatMap and concatMap was to find a replacement for nested subscriptions in angular using the http library.
createObs1().subscribe( (resp1) => {
console.log(resp1);
createObs2().subscribe( (resp2) => {
console.log(resp2);
createObs3().subscribe( (resp3) => {
console.log(resp3);
createObs4().subscribe( (resp4) => {
console.log(resp4);
createObs5().subscribe( (resp5) => {
console.log(resp5);
})
})
})
})
})
Your test scenario is not really sufficient to see the differences between these two operators. In your test case, each observable only emits 1 time. If an observable only emits a single value, there is really no different between concatMap and flatMap (aka mergeMap). The differences can only be seen when there are multiple emissions.
So, let's use a different scenario. Let's have a source$ observable that simply emits an incrementing integer every 1 second. Then, within our "Higher Order Mapping Operator" (concatMap & mergeMap), we will return an observable that emits a variable number of times every 1 second, then completes.
// emit number every second
const source$ = interval(1000).pipe(map(n => n+1));
// helper to return observable that emits the provided number of times
function inner$(max: number, description: string): Observable<string> {
return interval(1000).pipe(
map(n => `[${description}: inner source ${max}] ${n+1}/${max}`),
take(max),
);
}
Then let's define two separate observables based on the source$ and the inner$; one using concatMap and one using flatMap and observe the output.
const flatMap$ = source$.pipe(
flatMap(n => inner$(n, 'flatMap$'))
);
const concatMap$ = source$.pipe(
concatMap(n => inner$(n, 'concatMap$'))
);
Before looking the differences in the output, let's talk about what these operators have in common. They both:
subscribe to the observable returned by the passed in function
emit emissions from this "inner observable"
unsubscribe from the inner observable(s)
What's different, is how they create and manage inner subscriptions:
concatMap - only allows a single inner subscription at a time. As it receives emissions, it will only subscribe to one inner observable at a time. So it will initially subscribe to the observable created by "emission 1", and only after it completes, will it subscribe to the observable created by "emission 2". This is consistent with how the concat static method behaves.
flatMap (aka mergeMap) - allows many inner subscriptions. So, it will subscribe to the inner observables as new emissions are received. This means that emissions will not be in any particular order as it will emit whenever any of its inner observables emit. This is consistent with how the merge static method behaves (which is why I personally prefer the name "mergeMap").
Here's a StackBlitz that shows the output for the above observables concatMap$ and mergeMap$:
Hopefully, the above explanation helps to clear up your questions!
#1 - "use of flatMap should mix the outputs"
The reason this wasn't working as you expected was because only one emission was going through the flatMap, which means you only ever had a single "inner observable" emitting values. As demonstrated in the above example, once flatMap receives multiple emissions, it can have multiple inner observables that emit independently.
#2 - "...and include the code with multiple emitted events then things get messy."
The "things get messy" is due to having multiple inner subscription that emit values.
For the part you mention about using concatMap and still getting "mixed" output, I would not expect that. I have seen weird behavior in StackBlitz with observable emissions when "auto save" is enabled (seems like sometimes it doesn't completely refresh and old subscriptions seem to survive the auto refresh, which gives very messy console output). Maybe code sandbox has a similar problem.
#3 - "The reason I have gone to flatMap and concatMap was to find a replacement for nested subscriptions in angular using the http library"
This makes sense. You don't want to mess around with nested subscriptions, because there isn't a great way to guarantee the inner subscriptions will be cleaned up.
In most cases with http calls, I find that switchMap is the ideal choice because it will drop emissions from inner observables you no longer care about. Imagine you have a component that reads an id from a route param. It uses this id to make an http call to fetch data.
itemId$ = this.activeRoute.params.pipe(
map(params => params['id']),
distinctUntilChanged()
);
item$ = this.itemId$.pipe(
switchMap(id => http.get(`${serverUrl}/items/${id}`)),
map(response => response.data)
);
We want item$ to emit only the "current item" (corresponds to the id in the url). Say our UI has a button the user can click to navigate to the next item by id and your app finds itself with a click-happy user who keeps smashing that button, which changes the url param even faster than the http call can return the data.
If we chose mergeMap, we would end up with many inner observables that would emit the results of all of those http calls. At best, the screen will flicker as all those different calls come back. At worst (if the calls came back out of order) the UI would be left displaying data that isn't in sync with the id in the url :-(
If we chose concatMap, the user would be forced to wait for all the http calls to be completed in series, even though we only care about that most recent one.
But, with switchMap, whenever a new emission (itemId) is received, it will unsubscribe from the previous inner observable and subscribe to the new one. This means it will not ever emit the results from the old http calls that are no longer relevant. :-)
One thing to note is that since http observables only emit once, the choice between the various operators (switchMap, mergeMap, concatMap) may not seem to make a difference, since they all perform the "inner observable handling" for us. However, it's best to future-proof your code and choose the one that truly gives you the behavior you would want, should you start receiving more than a single emission.
Every time the first observable emits, a second observable is created in the flatMap and starts emitting. However, the value from the first observable is not passed along any further.
Every time that second observable emits, the next flatMap creates a third observable, and so on. Again, the original value coming into the flatMap is not passed along any further.
createObs1()
.pipe(
flatMap(() => createObs2()), // Merge this stream every time prev observable emits
flatMap(() => createObs3()), // Merge this stream every time prev observable emits
flatMap(() => createObs4()), // Merge this stream every time prev observable emits
flatMap(() => createObs5()), // Merge this stream every time prev observable emits
)
.subscribe((resp) => console.log(resp));
// OUTPUT:
// 5
So, it's only the values emitted from createObs5() that actually get emitted to the observer. The values emitted from the previous observables have just been triggering the creation of new observables.
If you were to use merge, then you would get what you may have been expecting:
createObs1()
.pipe(
merge(createObs2()),
merge(createObs3()),
merge(createObs4()),
merge(createObs5()),
)
.subscribe((resp) => console.log(resp));
// OUTPUT:
// 5
// 4
// 3
// 2
// 1

One time operation on First subscription of observable

I am using Rxjs. I have one observable and multiple subscription from different sources. I wish to trigger one function only once after getting first subscription to the observable. Is there any way to achieve this?
Not sure if this fits your scenario, but I have dealt with this in the past as well and found it best to conditionally return a different observable after checking for an initialization variable of some kind. Below is a working example of what I mean.
Component wants a list of states from an API
this.statesService.getStates()
.subscribe((states) => this.states = states);
Service wants to only get the states once from the API
private _states: IState[];
getStates(): Observable<IState[]> {
if (!this._states) {
// we don't have states yet, so return an observable using http
// to get them from the API
// then store them locally using tap
return this.http.get<IState[]>('/options/states').pipe(
tap((answer) => {
this._states = answer;
}),
);
} else {
// subsequent calls will just return an observable of the data needed
return of(this._states);
}
}
In the case above, it's easy to return a conditional observable. Hopefully this provides you some ideas on how to handle your conditional (only on the first subscribe) scenario.
You can use publishReplay(1), refCount() operators. It will ensure to evaluate observable only once and share the same result to all subscribers.

In RxJS, why does a pipe get executed once for each subscription?

I want to have multiple subscriptions to react to an observable event, but I want to log the event as well, so I pipe it through a do() operator in which I do the logging.
The problem is, the event gets logged once for each of the subscriptions I create!
I'm getting around this at the moment by creating a Subject and calling next on it from an event callback, which allows me to log the event once and trigger multiple subscriptions as well.
Here is some code that demonstrates the issue: https://stackblitz.com/edit/rxjs-xerurd
I have a feeling I'm missing something, isn't there a more "RxJS" way of doing this?
EDIT:
I'm not asking for a difference between hot & cold observable, in fact I was using a hot observable - the one created by fromEvent() and was wondering why does my presumably hot event source behave like it's cold.
I realize now - after reading about share() - that pipe() "turns" your observable cold i.e. returns a cold one based on the your source (which may be cold, may be hot)
Because Observable sequences are cold by default, each subscription will have a separate set of site effects.
If you want your side effect to be executed only once - you can share subscription by broadcasting a single subscription to multiple subscribers. To do this you can use share, shareReplay, etc.
To better understand how it works, what is "cold" and publish, refer to the RxJS v4 documentation:
4.8 Use the publish operator to share side-effects
EDIT : share() is finally working. Please have a look to the comments below. Thx to #Oles Savluk.
I let my answer below for the records. It may help.
share() and multicasting stuffs did not solve my very similar issue.
Here is how I solved it : https://stackblitz.com/edit/rxjs-dhzisp
const finalSource = new Subject();
fromEvent(button3, "click").pipe(
tap(() => {
console.log("this happens only once")
})
).subscribe(() => finalSource.next())
finalSource.subscribe(
() => console.log("first final subscription")
)
finalSource.subscribe(
() => console.log("second final subscription")
)
finalSource.subscribe(
() => console.log("third final subscription")
)

Subject vs BehaviorSubject vs ReplaySubject in Angular

I've been looking to understand those 3:
Subject
BehaviorSubject
ReplaySubject
I would like to use them and know when and why, what are the benefits of using them and although I've read the documentation, watched tutorials and searched google I've failed to make any sense of this.
So what are their purpose? A real-world case would be most appreciated it does not have to even code.
I would prefer a clean explanation not just "a+b => c you are subscribed to ...."
Thank you
It really comes down to behavior and semantics. With a
Subject - a subscriber will only get published values that were emitted after the subscription. Ask yourself, is that what you want? Does the subscriber need to know anything about previous values? If not, then you can use this, otherwise choose one of the others. For example, with component-to-component communication. Say you have a component that publishes events for other components on a button click. You can use a service with a subject to communicate.
BehaviorSubject - the last value is cached. A subscriber will get the latest value upon initial subscription. The semantics for this subject is to represent a value that changes over time. For example a logged in user. The initial user might be an anonymous user. But once a user logs in, then the new value is the authenticated user state.
The BehaviorSubject is initialized with an initial value. This is sometimes important to coding preference. Say for instance you initialize it with a null. Then in your subscription, you need to do a null check. Maybe OK, or maybe annoying.
ReplaySubject - it can cache up to a specified number of emissions. Any subscribers will get all the cached values upon subscription. When would you need this behavior? Honestly, I have not had any need for such behavior, except for the following case:
If you initialize a ReplaySubject with a buffer size of 1, then it actually behaves just like a BehaviorSubject. The last value is always cached, so it acts like a value changing over time. With this, there is no need for a null check like in the case of the BehaviorSubject initialized with a null. In this instance, no value is ever emitted to the subscriber until the first publishing.
So it really comes down to the behavior you are expecting (as for which one to use). Most of the time you will probably want to use a BehaviorSubject because what you really want to represent is that "value over time" semantic. But I personally don't see anything wrong with the substitution of ReplaySubject initialized with 1.
What you want to avoid is using the vanilla Subject when what you really need is some caching behavior. Take for example you are writing a routing guard or a resolve. You fetch some data in that guard and set it in a service Subject. Then in the routed component you subscribe to the service subject to try to get that value that was emitted in the guard. OOPs. Where's the value? It was already emitted, DUH. Use a "caching" subject!
See also:
What are RxJS Subject's and the benefits of using them?
Subject: On subscribing it always gets the data which is pushed after it's subscription i.e. previous pushed values are not received.
const mySubject = new Rx.Subject();
mySubject.next(1);
const subscription1 = mySubject.subscribe(x => {
console.log('From subscription 1:', x);
});
mySubject.next(2);
const subscription2 = mySubject.subscribe(x => {
console.log('From subscription 2:', x);
});
mySubject.next(3);
subscription1.unsubscribe();
mySubject.next(4);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>
With this example, here’s the result that’ll be printed in the console:
From subscription 1: 2
From subscription 1: 3
From subscription 2: 3
From subscription 2: 4
Note how subscriptions that arrive late are missing out on some of the data that’s been pushed into the subject.
Replay subjects: can help by keeping a buffer of previous values that will be emitted to new subscriptions.
Here’s a usage example for replay subjects where a buffer of 2 previous values are kept and emitted on new subscriptions:
const mySubject = new Rx.ReplaySubject(2);
mySubject.next(1);
mySubject.next(2);
mySubject.next(3);
mySubject.next(4);
mySubject.subscribe(x => {
console.log('From 1st sub:', x);
});
mySubject.next(5);
mySubject.subscribe(x => {
console.log('From 2nd sub:', x);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>
Here’s what that gives us at the console:
From 1st sub: 3
From 1st sub: 4
From 1st sub: 5
From 2nd sub: 4
From 2nd sub: 5
Behavior subjects: are similar to replay subjects, but will re-emit only the last emitted value, or a default value if no value has been previously emitted:
const mySubject = new Rx.BehaviorSubject('Hey now!');
mySubject.subscribe(x => {
console.log('From 1st sub:', x);
});
mySubject.next(5);
mySubject.subscribe(x => {
console.log('From 2nd sub:', x);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>
And the result:
From 1st sub: Hey now!
From 1st sub: 5
From 2nd sub: 5
Reference: https://alligator.io/rxjs/subjects/
A handy summary of the different observable types, non intuitive naming i know lol.
Subject - A subscriber will only get published values thereon-after the subscription is made.
BehaviorSubject - New subscribers get the last published value OR initial value immediately upon subscription.
ReplaySubject - New subscribers get all previously published value(s) immediately upon subscription
Most upvoted answer is plainly wrong claiming that:
"If you initialize a ReplaySubject with a buffer size of 1, then it actually behaves just like a BehaviorSubject"
This is not totally true; check this great blog post on differences between those two. For example if you subscribe to a completed BehaviorSubject, you won’t receive the last value but for a ReplaySubject(1) you will receive the last value.
This is am important difference that should not be overlooked:
const behavior = new BehaviorSubject(null);
const replay = new ReplaySubject(1);
behavior.skip(1).subscribe(v => console.log('BehaviorSubject:', v));
replay.subscribe(v => console.log('ReplaySubject:', v));
behavior.next(1);
behavior.next(2);
behavior.complete();
behavior.subscribe(v => console.log('Late B subscriber:', v));
replay.next(1);
replay.next(2);
replay.complete();
replay.subscribe(v => console.log('Late R subscriber:', v));
Check this code example here which comes from another great blog post on the topic.
From: Randall Koutnik's book “Build Reactive Websites with RxJS.” :
A Subject is an object that’s a turbocharged observable. At its core, a Subject acts much like a regular observable, but each subscription is hooked into the same source. Subjects also are observers and have next, error, and done methods to send data to all subscribers at once. Because subjects are observers, they can be passed directly into a subscribe call, and all the events from the original observable will be sent through the subject to its subscribers.
We can use the ReplaySubject to track history. A ReplaySubject records the last n events and plays them back to every new subscriber. For example in a chat applications. We can use it for tracking the record of previous chat history.
A BehaviorSubject is a simplified version of the ReplaySubject.
The ReplaySubject stored an arbitrary number of events, the BehaviorSubject only records the value of the latest event. Whenever a BehaviorSubject records a new subscription, it emits the latest value to the subscriber as well as any new values that are passed in. The BehaviorSubject is useful when dealing with single units of state, such as configuration options.
As mentioned in some of the posts, the accepted answer is wrong since BehaviorSubject != ReplaySubject(1) and it's not just a preference of coding style.
In the comments often the "guards" are mentioned and that's also where I most often found the use case for the Replay subjects. More specifically if you have a take(1) like scenario and you don't just want to take the initial value.
Check for example the following:
ngOnInit() {
const behaviorSubject = new BehaviorSubject<boolean>(null);
const replaySubject = new ReplaySubject<boolean>(1);
this.checkLoggedIn(behaviorSubject, 'behaviorSubject');
this.checkLoggedIn(replaySubject, 'replaySubject');
behaviorSubject.next(true);
replaySubject.next(true);
}
checkLoggedIn($userLoggedIn: Observable<boolean>, id: string) {
$userLoggedIn.pipe(take(1)).subscribe(isLoggedIn => {
if (isLoggedIn) {
this.result[id] = 'routed to dashboard';
} else {
this.result[id] = 'routed to landing page';
}
});
}
with the result:
{
"behaviorSubject": "routed to landing page",
"replaySubject": "routed to dashboard"
}
In those cases clearly you'd want a ReplaySubject! Working code: https://stackblitz.com/edit/replaysubject-vs-behaviorsubject?file=src%2Fapp%2Fapp.component.ts
// ***********Subject concept ***********
let subject = new Subject<string>();
subject.next("Eureka");
subject.subscribe((data) => {
console.log("Subscriber 1 got data >>>>> "+ data);
});
subject.subscribe((data) => {
console.log("Subscriber 2 got data >>>>> "+ data);
});
// ********behaviour subject*********
// Behavior subjects need a first value
let subject1 = new BehaviorSubject<string>("First value");
subject1.asObservable().subscribe((data) => {
console.log("First subscriber got data behaviour subject>>>>> "+ data);
});
subject1.next("Second value")
Subject - A subscriber will only get published values thereon-after the subscription is made.
BehaviorSubject - New subscribers get the last published value OR initial value immediately upon subscription.
Another difference is you can use the value getter of BehaviorSubject to get the current value. This is very useful when you need just current value in certain circumstances. For example, when a user clicks something and you need the value only once. In this case, you don't need to subscribe and then unsubscribe suddenly. The only need is:
BehaviorSubject bSubject = new BehaviorSubject<IBasket>(basket);
getCurrentBasketValue() {
return this.bSubject.value;
}

Reconnecting a WebSocket with a shared RxJS observable

I have an observable like this:
const records$ =
Rx.DOM.fromWebSocket('ws://192.168.2.4:9001/feed/', null)
.map(ev => parseRecord(ev.data))
.share();
I have many subscribers. When the connection is lost, all subscribers unsubscribe:
let records$Subscription;
records$Subscription = records$.subscribe(
record => { ... },
error => records$Subscription.dispose()
);
I verified that the call to dispose is indeed being made once for every subscription. Therefore, the share refcount has reached zero.
However, when I now subscribe to records$ again, no new WebSocket connection is set up. When I remove the call to share, however, it is. How come this doesn't work as expected with share?
I believe in rxjs v5, share does allow you to reconnect but not in Rxjs v4.
In Rxjs 4, share is bascially multicast.refCount and once the subject used for the multicast is completed, it cannot be reused (per Rxjs grammar rules, have a look at What are the semantics of different RxJS subjects? too), leading to the behaviour you observed.
In Rxjs 5, it uses a subject factory (something like multicast(() => new Rx.Suject().refCount())), so a subject is recreated when necessary.
See issues here and here for more details.
In short, if you can't do with the current behavior, you can switch to the v5 (note that it is still in beta and there are some breaking changes).

Categories

Resources