When should I create a new Subscription for a specific side effect?
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