Transforming a dynamic array into a new dynamic array (with a different data type) based on a property that can change over time

Explaining my process with the example of a REST api returning a list of objects, each needing a checkbox for selection and further processing. The api may be called multiple times for data refresh, utilizing Observables to handle selected values as an Observable.

Check out this Plunker: Receiving an observable in my Component with an array of objects (simplified strings):

var observable = Observable.of(["Hello", "World"]);

Creating objects of type Item based on this, storing object info with checked status as an Observable/BehaviorSubject:

this.items$ = observable.map(arr => arr.map(s => new Item(s)))
//...
class Item {
   public readonly isChecked$ : BehaviorSubject<boolean> = new BehaviorSubject(false);

   constructor(public readonly value : string) { }
}

Displaying them using [(ngModel)] binding from How to two-way bind my own RxJS Subject to an [(ngModel)]? :

<li *ngFor="let item of items$ | async as items" >
   <input id="cb-{{item.value}}" type="checkbox" [ngModel]="item.isChecked$ | async" (ngModelChange)="item.isChecked$.next($event)"  />
   <label for="cb-{{item.value}}">{{item.value}}</label>
</li>

Struggling to get an Observable<string[]> of selected values from an Observable<Item[]>, needing to access, filter, and convert back to an Observable array.

  1. Accessing and filtering observables of array items (item.isSelected$)
  2. Converting the result to an Observable by collecting values in an array

Seeking a more elegant solution in rxjs without manually handling a second array like in the plunker code snippet provided:

this.items$
    .switchMap(items => items.map(item => item.isChecked$.map(isChecked => ({ checked: isChecked, value: item.value}))))
    .mergeAll()
    .subscribe(o => {
        var selected = this._selectedItems.slice(0);
        if (o.checked)
            selected.push(o.value);
        else {
            var index = selected.indexOf(o.value);
            selected.splice(index, 1);
        }

        this._selectedItems = selected;
        this._selectedItems$.next(this._selectedItems);
    });

Any suggestions for a more streamlined approach in rxjs to achieve this outcome?

Answer №1

If you are looking to add values to a different observable, the recommended approach would be to employ a Subject. A Subject performs the dual role of observer and observable.

To insert values into the new observable using your filter, you can utilize the next() method similar to this

mySubject.next(val)

Another section of your code can then subscribe to this subject and respond as necessary

Answer №2

One approach is to maintain a BehaviorSubject that stays current with your list of items. This way, you can easily link checkboxes to the status of all items.

const item_data$ = new BehaviorSubject([]); // BehaviorSubject<Item[]>
items$.scan((acc, item) => acc.push(item), [])
      .subscribe(item_data$);

const checked_data$ = new Subject(); // Subject<string[]>
items$.subscribe(item =>
  item.isChecked$.subscribe(_ => 
    checked_subject.next(
      item_data$.getValue()
                   .filter(inner_item => inner_item.isChecked$.getValue())
                   .map(inner_item => inner_item.value)
    )
  )
);

Timing:

The use of checked_data$ should prevent false positives because of the order in which isChecked$ updates and emits its value to subscribers. The only potential issue could be if there is a race condition between the addition of a new item into item_data through scan and the change in that item's state. However, this scenario is unlikely given that users would need to click instantly as the new items appear on the screen.

Answer №3

When encountering such issues, my go-to solution is utilizing the Angular Material CDK Collection.

With this collection, it becomes easy to selectively choose or unselect items and stay updated with the model reactively.

const selectionModel = new SelectionModel(
  true,   // allowing multiple selections
  [2,1,3] // initial selected values
);

// selecting a value
selectionModel.select(4);
console.log(selectionModel.selected.length) //->  4

// unselecting a value
selectionModel.deselect(4);
console.log(selectionModel.selected.length) //->  3

// toggling a value
selectionModel.toggle(4);
console.log(selectionModel.selected.length) //->  4

// check if a value is selected
console.log(selectionModel.isSelected(4)) //-> true

// sorting the selections
console.log(selectionModel.sort()) //-> [1,2,3,4]

// subscribing to changes
selectionModel.changed.subscribe(s => console.log(s));

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

What is the process for updating the background color of the header in ngx datatable?

I am looking to change the background color of the table header to blue. This is the HTML code I have: <ngx-datatable class="material" [rows]="rows" [columnMode]="'force'" [headerHeight]="50" [footerHe ...

Tips for managing update logic in the server side with sveltekit

Currently, I am using Sveltekit and I am facing a dilemma regarding updating input data. The actual update process is straightforward, but there is an issue that arises when trying to send an update API request immediately, as it requires an accessToken to ...

Compiling Typescript upon saving in Sublime Text 3

Seeking a solution: How can I get Sublime Text 3 to automatically compile Typescript code when I save it? Having to switch between the terminal and Sublime is getting tedious. Appreciate any advice, thank you! ...

Error: Unable to locate loading.gif file within the Angular library

In my current setup, I have a monorepo that includes a standard Angular application and an Angular workspace with a library at the same level as the app. After configuring the main app to recognize the library, I am able to use the components without any ...

Having trouble making API calls in an Angular application

When attempting to call the api using the httpClient method, I encountered an issue where the result was returning as undefined. Below is the code from the services file. contact-service.services.ts import { Injectable } from '@angular/core'; ...

ESLint not functioning properly on TypeScript (.ts and .tsx) files within Visual Studio Code

After installing the ESLint extension in VSC, I encountered an issue where it was no longer working on the fly for my React project when I introduced Typescript. In the root of my project, I have a .eslintrc file with the following configuration: { "pa ...

What is the method for retrieving array values from an attribute?

I am currently developing an Angular 6 application and I need to pass and retrieve array values dynamically through attributes. Here is the code snippet I have used for this purpose: HTML: <ul class="list-unstyled" id="list" [attr.parent_id]="123"> ...

Nested self-referencing in Typescript involves a structure where

Please note that the code below has been simplified to highlight a specific issue. The explanation before the code may be lengthy, but it is necessary for clarity. Imagine I have a Foo class that represents a complex object. interface Config { bars:{ ...

When utilizing Angular CDK virtual scroller, an error occurs stating that @angular/core/core does not have an exported member 'ɵɵFactoryDeclaration'. Can anyone explain why this is happening

After adding CDK Virtual Scroller to my ionic 5.3.3 project using the command: npm add @angular/cdk The version installed is: "@angular/cdk": "^13.0.2" The scroller viewport contains an ion-item-group: <ng-template #showContentBlo ...

Using the transform property with the scale function causes elements positioned in the bottom right corner to vanish

Issue specific to Google Chrome and Windows 10 I'm currently working on a flipbook that adjusts content size using transform:scale() based on the user's screen size. There is also a zoom feature that allows users to adjust the scale factor. I ha ...

What could be causing the ng-if function to continuously loop?

I have encountered an issue where I am passing a boolean function in the ngIf attribute instead of a boolean condition in my HTML template file. This boolean function seems to be repeating itself depending on the amount of data present in the variable &apo ...

There was a problem identified in the file node_modules/@types/jqueryui/index.d.ts at line 645, column 11. The issue was reported

I encountered an issue with npm run build while using docker build. When I initially developed the project around 2 years ago, it worked perfectly with npm run build. However, upon rebuilding my development environment recently, I started encountering this ...

Ways to establish connections between numerous m-n relationship entries

Here is the schema I am working with: model School { id Int @id @default(autoincrement()) name String teachers Teacher[] students Student[] } model Teacher { id Int @id @default(autoincrement()) firstName String ...

What is the best way to combine data from two rows into one row?

Can someone help me with merging 2 sets of Line data into a single row for each entry? For example, the 'Green Bay Packers @ Chicago Bears' row should display '+3.5000 and -3.5000', while 'Atlanta @ Minnesota' should show &apo ...

Guide on transferring information obtained from a service to an Angular datatable

Recently, I started working on Angular 4 and encountered an issue while trying to display data from an Angular service in JSON format using angular-datatable. Despite trying various options, I am unable to get the data to display within the columns of the ...

Running ng build --prod does not compile the source code

Upon running the following command: ng build --prod utilizing the following versions: node version: v6.11.0 @angular/cli: 1.0.0-rc.2 typescript: Version 2.4.2 encountered errors as shown below: ERROR in ./src/main.ts Module not found: Error: Can ...

Adding a value to an array in TypeScript

When trying to add values to an array in my code, I encountered an error stating that "number" is not a valid type for the array. someArray: Array <{ m: number, d: Date}> = []; this.someArray.push(500,new Date(2020,1,15)); ...

The ngOnInit lifecycle hook is not triggered by the Angular routerLink

In the component.ts file, you will find the ngOnInit function as shown below: ngOnInit() { this.locationService.getLocation().subscribe( locations => { this.locations = locations; }); } <a [routerLink]="['/locations-list&apo ...

My goal is to prevent users from using the Backspace key within the input field

Let's say we want to prevent users from using the backspace key on an input field in this scenario. In our template, we pass the $event like so: <input (input)="onInput($event)"> Meanwhile, in our app.component.ts file, the function ...

How to eliminate all special characters from a text in Angular

Suppose I receive a string containing special characters that needs to be transformed using filter/pipe, with the additional requirement of capitalizing the first letter of each word. For instance, transforming "@!₪ test stri&!ng₪" into "Test Stri ...