Is there a specific side effect that warrants creating a new Subscription?

Recently, I had a discussion on Stack Overflow regarding RxJS and the best approach for handling subscriptions in a reactive application. The debate was whether it's better to create a subscription for each specific side effect or minimize subscriptions in general. This topic is crucial for understanding how to structure a full reactive application and knowing when to switch between approaches. Clarifying this can help not only me but also others in avoiding unnecessary debates.

Setup Information

  • All examples provided are in TypeScript
  • To maintain focus on the question, no usage of lifecycles/constructors for subscriptions is included to keep it framework-agnostic
    • Assume that subscriptions are added in constructor/lifecycle init
    • Assume that unsubscribing is done in lifecycle destroy

Defining Side Effects (Angular Example)

  • UI Update/Input (e.g. value$ | async)
  • Output/Upstream of a component (e.g. @Output event = event$)
  • Interaction between different services with varying hierarchies

Example Use Case:

  • Two functions:
    foo: () => void; bar: (arg: any) => void
  • Two source observables:
    http$: Observable<any>; click$: Observable<void>
  • foo is called after http$ emits without needing a value
  • bar is called after click$ emits, requiring the current value of http$

Case Study: Creating a Subscription for Each 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 Study: Minimizing Subscriptions in General

http$.pipe(
  tap(() => foo()),
  switchMap(httpValue => click$.pipe(
    mapTo(httpValue )
  )
).subscribe(bar);

Personal Opinion Summarized

I believe that while subscriptions may initially complicate the Rx landscape by adding considerations like sharing your observable or not, separating your code based on what occurs when makes it easier to maintain, test, debug, and update in the future. Personally, I prefer creating a single observable source and individual subscriptions for each side effect in my code. If multiple side effects stem from the same observable source, I opt to share the observable and subscribe individually for each effect due to potentially differing lifecycles.

Answer №1

RxJS is a powerful tool for handling asynchronous operations and can be used to simplify code by reducing the number of subscriptions where possible. It's important not to automatically subscribe to an observable if RxJS offers a better solution that can minimize subscriptions in your application.

There are exceptions to this rule, such as when reusing observables in a single template:

For example - reusing observables in a single template

Consider the following scenario:

// Component:

this.value$ = this.store$.pipe(select(selectValue));

// Template:

<div>{{value$ | async}}</div>

If value$ is only used once in a template, using the async pipe can be beneficial for simplicity and automatic unsubscription. However, multiple references to the same async variable in a template should be avoided, as noted in this answer.

// It works, but it's not recommended...

<ul *ngIf="value$ | async">
    <li *ngFor="let val of value$ | async">{{val}}</li>
</ul>

In this case, creating a separate subscription and updating a non-async variable in the component may be a better choice:

// 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>

While it's technically possible to achieve the same result without valueSub, the context of the application justifies this approach.

Considering the role and lifespan of an observable before deciding whether to subscribe

If two or more observables are only meaningful when combined, use appropriate RxJS operators to merge them into one subscription.

Furthermore, prioritizing code efficiency and avoiding 'extra' subscriptions becomes more critical when using first() to filter out excess emissions compared to ongoing session-based observables.

If individual observables have standalone utility apart from others, separate subscriptions might offer flexibility and clarity. Ultimately, subscriptions should be justified on a case-by-case basis rather than automatic.

Regarding Unsubscriptions:

One downside of additional subscriptions is the need for more unsubscribing. While we aim to handle all necessary unsubscriptions at onDestroy, real-world scenarios may introduce challenges. RxJS features like first() streamline this process, enhancing code quality and mitigating memory leaks. Refer to relevant articles for insights and examples.

Personal preference / verbosity vs. terseness:

Your personal coding style plays a role in determining the right balance between verbosity and succinctness. Strive to find a middle ground without cluttering code with unnecessary details or sacrificing readability. Explore discussions on code verbosity for further insights.

Answer №2

When aiming to optimize subscriptions, why not take it a step further and follow this specific approach:

 const obs1$ = src1$.pipe(tap(effect1))
 const obs2$ = src2$pipe(tap(effect2))
 merge(obs1$, obs2$).subscribe()

The strategy of only using tap for side effects and merging the observables results in having only one subscription active.

However, there is a downside to this method as it limits the full potential of RxJS. This library's strength lies in its ability to compose observable streams, and dynamically subscribe or unsubscribe from them when necessary.

It is important to keep your observables logically organized and avoid cluttering them just to reduce the number of subscriptions. Are the 'foo' and 'bar' effects inherently linked? Does triggering one always mean triggering the other? Should the emission of http$ always trigger the 'foo' effect? By combining unrelated functions into one stream, you may be creating unnecessary dependencies.

Additionally, managing errors becomes more complex with a single subscription model in my opinion.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Exploring the world of form interactions in Angular: A guide to creating dynamic element communication

I have created a form using Angular, and I want to display a specific value in my input field when an element is selected from the dropdown. Additionally, since the values in the dropdown are fetched from a server, I want to show a corresponding label for ...

Attempting to display a collection of 16 diverse images by utilizing the Math.random function in conjunction with a

I have been attempting to retrieve 16 random images, but I keep getting duplicates. I am aware that a modification needs to be made to the one => one.number filter, however all attempts so far have been unsuccessful. import React from "react"; import ...

Tips for updating the state of an individual component in ReactJS without having to re-render the entire component

As a beginner in ReactJS, I am seeking guidance on how to modify the state of a specific component that was created within a Map function. Imagine I have a basic component named panels, with multiple panel-items. Each panel-item is essentially one componen ...

Encountering an error in accessing my own API: "Cannot read property 'then

I'm currently in the process of using my personal express.js API with nodejs. Although it is functioning, I am encountering an error that is preventing me from accessing the response of the request. Below is a snippet of my code: routes.js: app.post ...

Tips for building an interactive button with changing content and retrieving variable information when the button is clicked in an Angular framework

Received dynamic data from the server is shown below: { "data": [ { "id": 4, "first_name": "Eve", "last_name": "Holt", "lat":"25.6599899", "lng":"45.3664646", "status":"0" ...

Modifying an item within an array of Mongoose models

I am working with a model schema that looks like this: { _id: foo cart: { items: [ { id: number name: string, } ] } } My goal is to locate the document by its id and then modify the name value of the object in ...

Trouble with retrieving dates in ISO format

My collection stores the date of birth field in ISO format, for example: ISODate("1980-01-01T20:20:19.198Z") However, when I use a date time picker on my HTML page, it displays the date like this: 01/01/1980 Unfortunately, when querying for the date of ...

Problem with moving functions from one file to another file via export and import

I currently have the following file structure: ---utilities -----index.ts -----tools.ts allfunctions.ts Within the tools.ts file, I have defined several functions that I export using export const. One of them is the helloWorld function: export const hel ...

Prevent selection on a specific column in ngx-datatable

My ngx-datatable has 4 data columns and a delete button column to remove rows from the table. https://i.stack.imgur.com/MbGDM.png Here is the HTML code: <ngx-datatable *ngIf="!isLoading" #table class="data-table" [scrollbarH]="true" [rows]="data" [co ...

Guide on showing a message on the server side when pressing a button in the web browser

I need the following question to function seamlessly on all major browsers including Opera, Internet Explorer, Chrome, Safari, and Firefox I am working on an application that requires users to follow a specific order of pages Text1.php, Text2.php, Text3.p ...

The declared type 'never[]' cannot be assigned to type 'never'. This issue is identified as TS2322 when attempting to pass the value of ContextProvider using the createContext Hook

I'm encountering an issue trying to assign the state and setState to the value parameter of ContextProvider Here's the code snippet:- import React, { useState, createContext } from 'react'; import { makeStyles } from '@material-ui ...

Display issue with React TypeScript select field

I am working with a useState hook that contains an array of strings representing currency symbols such as "USD", "EUR", etc. const [symbols, setSymbols] = useState<string[]>() My goal is to display these currency symbols in a select field. Currently ...

Determine the maximum size of a specified number of square divs that can fit within responsive parent divs

I am working on creating a grid of square divs inside a container with variable height and width. The number of square div's is predetermined. All square divs will have images with the same dimensions (similar to the example). They should align left ...

retrieving data from a node and embedding it into an HTML file

In my project, I have an index.html file and an app.js nodejs file. Within the app.js file, there is a variable named "name" that I would like to display in my index.html page. Here is the code snippet: var name = "Utsav"; var HTTP = require('HTTP&a ...

Modify the innerHTML to adjust font size when a button is clicked in Ionic 5, or eliminate any unnecessary spaces

I have been experimenting with changing the font size of a variable in .html when the variable contains whitespace. In my .ts page, I use the following code to remove the whitespace: this.contents = this.sanitizer.bypassSecurityTrustHtml(this.product[&apos ...

What is the proper way to utilize a service within a parent component?

I need assistance with setting up inheritance between Child and Parent components. I am looking to utilize a service in the Parent component, but I have encountered an issue. When attempting to input the service in the Parent constructor like this: expor ...

Step-by-step guide on incorporating CSS box-shadow with the .style JavaScript property

I have a JavaScript code snippet that looks like this: document.getElementById("imgA").style.box-shadow = "0 0 5px #999999"; The hyphen in box-shadow is causing an invalid assignment exception to be thrown by the JavaScript engine (specifically in Firefo ...

The error message "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: The file extension ".ts" is not recognized for the file located at /Project/src/index

Whenever I run npm run dev, I keep encountering the following error: TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Project/src/index.ts. Despite having all my settings configured as recommended, Here is a snippet of my package ...

I'm looking for a way to have an element shift in a particular direction only when two keys are pressed simultaneously

Having trouble moving a square diagonally when two keys are pressed simultaneously. Trying to create an if statement that detects both key presses at the same time and triggers the movement. However, all attempts have failed so far, leaving uncertainty abo ...

Tips for customizing the default styles of PrimeNG's <p-accordion> tag

Hello, I have encountered an issue with my html code snippet. I am attempting to create a tab with a label-header named "Users", but the accordion tag consistently displays it as underlined. <div class="ui-g"> <p-accordion id="tabsHeader"> ...