Rxjs - switchMap to http changes observable character from hot to cold - javascript

Can somebody explain me why below code triggers request to server three times? if i were subscribing directly to http.get() i know that its cold observable so it will result callin server 3 times and i need to use .share() to avoid that. but why the same behavior when i am subscribing to subject which is hot.. weird:
let testTrigger = new Subject<any>();
let testTask$ = testTrigger.switchMap(()=> this.restClient.get('onet'));
testTask$.subscribe(console.log.call(console));
testTask$.subscribe(console.log.call(console));
testTask$.subscribe(console.log.call(console));
testTrigger.next(1);

In fact most operators would do the same. i.e. if obs is hot, then obs.op is in general cold. A few operators also returns hot observables (groupBy for example). In the end you need to read the documentation or test to see the nature of the observable you have in your hand.
For more details you can have a look at Hot and Cold observables : are there 'hot' and 'cold' operators?.

Change your code to:
let testTask$ = testTrigger.switchMap(()=> this.restClient.get('onet')).share();
This way all subscriptions will share the same pipe. I guess that when you subscribe you trigger the mapping (hence the http call).

Related

Search functionality issues using rxjs

I am trying to implement a search functionality.
In my component:
this.newsArticles$ = this.searchKeyChanged$.pipe(
debounceTime(200),
distinctUntilChanged(),
switchMap(searchKey =>
searchKey?.length > 0 ? this.newsService.getNewsArticlesByKey(searchKey) : this.newsService.getNewsArticles()
))
In my service:
getNewsArticles(): Observable<NewsArticle[]> { /// some http request to get all the news articles}
getNewsArticlesByKey(searchKey: string): Observable<NewsArticle[]> {
return this.getNewsArticles().pipe(map(articles => filterArticlesByKey(articles, searchKey)));
}
When the searchKey changes, the switchMap is not triggered somehow.
Any idea what is wrong here?
Have you subscribed to the newsArticles$ observable? Http requests are "Cold" Observables. This means they are lazily executed and so the request is only actually made when there is a subscriber. You should also be aware that this means if you subscribe to this Observable twice then you will get two HTTP requests unless you use something like the share operator.
I see two main issues that might occur during the flow:
Your distinctUntilChanged might block the emissions. Add a tap(v => console.log(v)) before distinctUntilChanged.
Your getNewsArticles() or getNewsArticlesByKey(key: string) might return an error and result the stream cancellation. In this case, think of changing switchMap to flatMap or concatMap - they don't have the cancelling effect.
More info here: https://rxjs-dev.firebaseapp.com/api/operators/switchMap

Does nested operators unsubscribe with the takeUntil?

Is the takeUntil will manage the subscription of the mergeMap when destroyed$ is emitted in the following code? In other words, is the mergeMap leak a subscription?
combineLatest([
this.selectedCustomerId$.pipe(
-->mergeMap<--(customerId => invoicesService.getInvoices(customerId)
),
this.configuration$
]).pipe(
takeUntil(this.destroyed$)
).subscribe([invoices, configuration] => this.displayInvoices(invoices, configuration));
Thanks!
Yes, there will be no memory leakage.
To understand that you need to understand how pipe works -
Without calling subscribe - there is no subscriptions, doesn't matter what operators you have put it, what pipe does, is it creates a new observable, which, while subscribing to, subscribe to the source observable, and while unsubscribing, it unsubscribe to the source observable.
Even though you have two pipes here - the "un-subscription" will bubble up. i.e. when destroyed$ emits the combine latest subscription will end, than, each of the combineLatest parameters subscription will end, than response of the pipe in which you used the mergeMap will end, which means the return value of the mergeMap will end too.
I hope it wasn't too complicated of an explanation, the short version is that when using pipe everything is connected so when you unsubscribe it unsubscribes all sources.

When should I create a new Subscription for a specific side effect?

Last week I answered an RxJS question where I got into a discussion with another community member about: "Should I create a subscription for every specific side effect or should I try to minimize subscriptions in general?" I want to know what methology to use in terms of a full reactive application approach or when to switch from one to another. This will help me and maybe others to avoid unecesarry discussions.
Setup info
All examples are in TypeScript
For better focus on question no usage of lifecycles/constructors for subscriptions and to keep in framework unrelated
Imagine: Subscriptions are added in constructor/lifecycle init
Imagine: Unsubscribe is done in lifecycle destroy
What is a side effect (Angular sample)
Update/Input in the UI (e.g. value$ | async)
Output/Upstream of a component (e.g. #Output event = event$)
Interacton between different services on different hierarchies
Exemplary usecase:
Two functions: foo: () => void; bar: (arg: any) => void
Two source observables: http$: Observable<any>; click$: Observable<void>
foo is called after http$ has emitted and needs no value
bar is called after click$ emits, but needs the current value of http$
Case: Create a subscription for every specific side effect
const foo$ = http$.pipe(
mapTo(void 0)
);
const bar$ = http$.pipe(
switchMap(httpValue => click$.pipe(
mapTo(httpValue)
)
);
foo$.subscribe(foo);
bar$.subscribe(bar);
Case: Minimize subscriptions in general
http$.pipe(
tap(() => foo()),
switchMap(httpValue => click$.pipe(
mapTo(httpValue )
)
).subscribe(bar);
My own opinion in short
I can understand the fact that subscriptions make Rx landscapes more complex at first, because you have to think about how subscribers should affect the pipe or not for instance (share your observable or not). But the more you separate your code (the more you focus: what happens when) the easier it is to maintain (test, debug, update) your code in the future. With that in mind I always create a single observable source and a single subscription for any side effect in my code. If two or more side effects I have are triggered by the exact same source observable, then I share my observable and subscribe for each side effect individually, because it can have different lifecycles.
RxJS is a valuable resource for managing asynchronous operations and should be used to simplify your code (including reducing the number of subscriptions) where possible. Equally, an observable should not automatically be followed by a subscription to that observable, if RxJS provides a solution which can reduce the overall number of subscriptions in your application.
However, there are situations where it may be beneficial to create a subscription that is not strictly 'necessary':
An example exception - reuse of observables in a single template
Looking at your first example:
// Component:
this.value$ = this.store$.pipe(select(selectValue));
// Template:
<div>{{value$ | async}}</div>
If value$ is only used once in a template, I'd take advantage of the async pipe and its benefits for code economy and automatic unsubscription. However as per this answer, multiple references to the same async variable in a template should be avoided, e.g:
// It works, but don't do this...
<ul *ngIf="value$ | async">
<li *ngFor="let val of value$ | async">{{val}}</li>
</ul>
In this situation, I would instead create a separate subscription and use this to update a non-async variable in my component:
// Component
valueSub: Subscription;
value: number[];
ngOnInit() {
this.valueSub = this.store$.pipe(select(selectValue)).subscribe(response => this.value = response);
}
ngOnDestroy() {
this.valueSub.unsubscribe();
}
// Template
<ul *ngIf="value">
<li *ngFor="let val of value">{{val}}</li>
</ul>
Technically, it's possible to achieve the same result without valueSub, but the requirements of the application mean this is the right choice.
Considering the role and lifespan of an observable before deciding whether to subscribe
If two or more observables are only of use when taken together, the appropriate RxJS operators should be used to combine them into a single subscription.
Similarly, if first() is being used to filter out all but the first emission of an observable, I think there is greater reason to be economical with your code and avoid 'extra' subscriptions, than for an observable that has an ongoing role in the session.
Where any of the individual observables are useful independently of the others, the flexibility and clarity of separate subscription(s) may be worth considering. But as per my initial statement, a subscription should not be automatically created for every observable, unless there is a clear reason to do so.
Regarding Unsubscriptions:
A point against additional subscriptions is that more unsubscriptions are required. As you've said, we would like assume that all necessary unsubscriptions are applied onDestroy, but real life doesn't always go that smoothly! Again, RxJS provides useful tools (e.g. first()) to streamline this process, which simplifies code and reduces the potential for memory leaks. This article provides relevant further information and examples, which may be of value.
Personal preference / verbosity vs. terseness:
Do take your own preferences into account. I don't want to stray towards a general discussion about code verbosity, but the aim should be to find the right balance between too much 'noise' and making your code overly cryptic. This might be worth a look.
if optimizing subscriptions is your endgame, why not go to the logical extreme and just follow this general pattern:
const obs1$ = src1$.pipe(tap(effect1))
const obs2$ = src2$pipe(tap(effect2))
merge(obs1$, obs2$).subscribe()
Exclusively executing side effects in tap and activating with merge means you only ever have one subscription.
One reason to not do this is that you’re neutering much of what makes RxJS useful. Which is the ability to compose observable streams, and subscribe / unsubscribe from streams as required.
I would argue that your observables should be logically composed, and not polluted or confused in the name of reducing subscriptions. Should the foo effect logically be coupled with the bar effect? Does one necessitate the other? Will I ever possibly not to want trigger foo when http$ emits? Am I creating unnecessary coupling between unrelated functions? These are all reasons to avoid putting them in one stream.
This is all not even considering error handling which is easier to manage with multiple subscriptions IMO

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

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