Data shared among various RXJS Observables

I am currently in the process of refactoring some code to utilize RXJS and the angular async pipe. The task at hand involves adding and removing items from an array. I'm wondering if there might be a more efficient approach to manipulating a shared array other than using tap to set store the state in a BehaviorSubject.

this.listSubject = new BehaviorSubject(['a','b','c']);
this.addSubject = new Subject<string>();
this.addAction$ = this.addSubject.asObservable().pipe(
            withLatestForm(this.listSubject),
            map(([itemToAdd, list]) => {list.push(itemToAdd); return list;}),
            tap((data) => this.listSubject.next(data))
        );
this.removeSubject = new Subject<number>();
this.removeAction$ = this.removeSubject.asObservable().pipe(
            withLatestForm(this.listSubject),
            map(([indexToRemove, list]) => {list.splice(indexToRemove, 1); return list;}),
            tap((data) => this.listSubject.next(data))
        );
this.list$ = merge(this.addAction$, this.removeAction$);

EDIT: The UI code is using async pipe,

list$ | async 

Answer №1

If you are looking for a solution, here is an example of how to achieve it:

export class HelloComponent {
  actionSubject = new Subject<Action>();
  action$ = this.actionSubject.asObservable();

  originalList$ = of(["a", "b", "c"]);

  list$ = merge(this.originalList$, this.action$).pipe(
    scan((acc: string[], action: any) => {
      if (action.isDelete) {
        return acc.filter(item => item !== action.item);
      } else {
        return [...acc, action.item];
      }
    })
  );

  onAdd() {
    this.actionSubject.next({ item: "z", isDelete: false });
  }

  onRemove() {
    this.actionSubject.next({ item: "b", isDelete: true });
  }
}

export interface Action {
  item: string;
  // This could instead be an Enum of operations
  isDelete: Boolean;
}

A demonstration of the code can be found in this Stackblitz link: https://stackblitz.com/edit/angular-array-processing-deborahk

The approach involved creating a single action stream that emits an object with information on whether to add or remove an item from the list.

Using the scan operator, the set of items is maintained over time by either adding to or removing from the list.

Your initial code resides in the app.component.ts file, while the revised version can be found in the hello.component.ts file.

Would this method meet your requirements?

Note: Utilizing the async pipe eliminates the need for subscribing directly. You can display the updated list like this:

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

Answer №2

Typically, the majority of usage involving the tap method is observed among devoted RxJS enthusiasts.

One major concern to address is the importance of modifying your application state (the behavior subject) using a subscription rather than a tap. If there are no subscribers to the list$, then the tap will not be initiated. Conversely, if there are two subscribers to the list$, the taps will execute twice for each event. To avoid this issue, consider utilizing a publish, or implement...

this.listSubject = new BehaviorSubject(['a','b','c']);
this.addSubject = new Subject<string>();
this.addSubject.subscribe(itemToAdd => {
  const currentValue = this.listSubject.value;
  currentValue.push(itemToAdd);
  this.listSubject.next(currentValue);
});
this.removeSubject = new Subject<number>();
this.removeSubject.subscribe(indexToRemove => {
  const currentValue = this.listSubject.value;
  currentValue.splice(indexToRemove, 1);
  this.listSubject.next(currentValue);
});

A secondary concern worth mentioning is the re-emission of your list from multiple subjects. By having three different subjects capable of emitting the same list, you introduce multiple sources of truth and violate CQS principles. Ideally, when calling addItem, grabbing the updated list value should not be necessary. The consumer of the list should automatically receive updates through the updated list subject.

For further exploration: In your application, state management involves the list state and events that can alter this state. This aligns with concepts like "stores" and "actions" in tools such as ngrx. Behavior subjects serve as a lightweight alternative to more comprehensive tools like the ngrx store. As your application complexity grows, delving into resources on tools like ngrx store could provide significant benefits.

Answer №3

What are the disadvantages of not implementing functions?

listData = new BehaviorSubject(['a','b','c']);

addValue(val) {
  this.listData.next([...this.listData.value, val]);
}

removeValue(val) {
  this.listData.next(...this.listData.value.filter(v => v !== val));
}

Your code lacks clarity in terms of subscriptions to observables. Subscribing to action observables is necessary for triggering functionality. Additionally, be cautious of mutating objects.

Answer №4

Your code looks fantastic! One suggestion I have is to consider incorporating immutability into it. This way, whenever you add or remove items from the list, a new list will be generated each time:

    this.addAction$ = this.addSubject.asObservable().pipe(
                withLatestForm(this.listSubject),
                map(([itemToAdd, list]) => [...list, itemToAdd]),
                tap((data) => this.listSubject.next(data))
            );

    this.removeAction$ = this.removeSubject.asObservable().pipe(
                withLatestForm(this.listSubject),
                map(([indexToRemove, list]) =>
[...list.slice(0, indexToRemove), ...list.slice(indexToRemove + 1)]),
                tap((data) => this.listSubject.next(data))
            );

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

The data source retrieved through the "get" API method is missing from the mat-table

Recently, I've started working with angularCLI and I'm facing an issue in creating a table where the dataSource is fetched from a fake API. Let me share my component class: import { Component, OnInit } from '@angular/core'; import { Fo ...

The Ocelot API Gateway is a powerful tool for managing

I encountered an issue while working on my API gateway project. I initially installed the latest version of Ocelot (16.0.1), but it did not function correctly. The problem was resolved by reverting back to Ocelot version 15.0.6, while keeping my .NET Core ...

Converting JSON to an array in Ionic 3

I'm struggling to convert a JSON response from a Laravel API into an array in my Ionic 3 app. Below is the JSON structure: https://i.sstatic.net/izRAV.png Object { id_oiseau: 1, nom_commun: "Hirondelle", lieu_signalement: "Foret", ...

Using TypeScript for Context Providers

I successfully implemented a basic context provider using ES6, but I am struggling to adapt it for use in a TypeScript project. Each resource I find online offers a different perspective on how to integrate the context api. My issue revolves around workin ...

Displaying nested data on an Angular 8 table

Hello everyone, I am currently dealing with an HTTP response from a MongoDB and extracting values like: loadOrders(){ this.orderService.getOrders() .subscribe((data:any[])=>{ this.orders=data; } ); } In the orders-li ...

Storing data retrieved from a GraphQL response into the sessionStorage: A step-by-step guide

I am facing a challenge in saving my GraphQL response in sessionStorage to access it across different parts of the application without making repeated API calls. I am currently using the useQuery hook with a skip property to check if the data is already st ...

Refreshing Angular2 View After Form Submission

Currently, I am in the process of developing a basic CRUD application with Angular2. The application comprises of a table that displays existing records and a form for adding new records. I am seeking guidance on how to update the table to show the new rec ...

What is the best way to modify the color of a table cell in Typescript with *ngFor loop?

I have an image located at the following link: https://i.sstatic.net/rxDQT.png My goal is to have cells with different colors, where main action=1 results in a green cell and action=0 results in a red cell. Below is the HTML code I am working with: & ...

NGC Error: Unable to locate the type definition file for 'rx/rx.all' - Please fix this issue

Recently, I've been working on some enhancements to the flex-layout project. While running ngc ./node_modules/.bin/ngc -p src/lib/tsconfig.json I encountered an issue... Error Cannot find type definition file for 'rx/rx.all'. It seems li ...

Invalid for the purpose of storage

Encountering the following error message: The dollar ($) prefixed field '$size' in 'analytics.visits.amounts..$size' is not valid for storage. return Manager.updateMany({}, { $push: { & ...

Angular Universal is experiencing difficulties resolving dependencies

After finally migrating my existing Angular project from v8 to v13.0.0, I decided to incorporate SSR into it. The process of updating the project itself was time-consuming and challenging. Once the app successfully ran on v13.0.0, I attempted to add SSR b ...

Needing to utilize the provide() function individually for every service in RC4

In Beta, my bootstrapping code was running smoothly as shown below: bootstrap(App, [ provide(Http, { useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, helperService: HelperService, authProvider: AuthProvider) => new CustomHt ...

Developing an Azure pipeline to generate artifacts for an Angular Universal application

I am currently facing an issue with deploying my Angular Universal app to Azure Web App Service. Running npm run build:ssr && npm run serve:ssr locally works perfectly fine. Similarly, running npm run start without ssr rendering also works smoothly ...

Using Karma-Jasmine to Import Spy without anyImplicitAny

If I include the configuration setting noImplicitAny in the tsconfig.json file of my Angular 4+ project: "noImplicitAny": true, ...and then try to import and use Spy in a unit test: import { Spy } from "karma-jasmine"; I encounter this console error wh ...

Encountering TypeScript errors while trying to implement Headless UI documentation

import { forwardRef } from 'react' import Link from 'next/link' import { Menu } from '@headlessui/react' const MyLink = forwardRef((props, ref) => { let { href, children, ...rest } = props return ( <Link href={href}&g ...

Tips on troubleshooting the issue when attempting to use a hook in your code

I am trying to implement a hook to manage the states and event functions of my menus. However, when I try to import the click function in this component, I encounter the following error: "No overload matches this call. The first of two overloads, '(p ...

Facing issue with Angular 17 where pipe is displaying empty data

I am currently utilizing Angular 17 with the code provided below: database.component.html @for(user of (users | userPipe:filters); track user.id) { <tr id="{{ user.id }}"> <td>{{ user.name }}</td> <td> ...

The properties required for type 'never[]' are not present

The type 'never[]' does not have the necessary properties from type '{ login: string; id: number; node_id: string; avatar_url: string; url: string; }': login, id, node_id, avatar_url, url When working on a component that takes an ApiUr ...

The function 'sendEmailVerification' is not a property of the type 'Promise<User>'

Hey there, I've been working on my ionic4 app and have encountered an issue with the sendEmailVerification function. The console is suggesting that I may have forgotten to use 'await'. Any ideas on how to resolve this? Thank you. import { In ...

Angular 2 - Bootstrap - Toggle Hide/Show

Is there a way to create a unique ID for each collapse item? The issue I'm facing is that when I click anywhere in the list, the first item always collapses. I've tried giving the div the data.id, but it doesn't seem to work. <div class ...