How can you add or remove an item from an array of objects in Angular/RXJS using Observables?

Purpose: The goal is to append a new object to an existing Observable array of objects and ensure that this change is visible on the DOM as the final step.

NewObject.ts:

export class NewObject {
  name: string;
  title: string;
}

Here's the example.component.ts:

import { Observable } from 'rxjs';
import { Component, OnInit, Inject, EventEmitter } from '@angular/core';
import { NewObject } from 'objects';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnInit {

  // Starting with an observable array of objects initialized from a service (step 1)
  readonly objects$: Observable<NewObject[]> = this.objectSvc.getAllObjects("objects").pipe(
    map(obj => obj.map(x => ({ name: x.name, title: alterString(x.title) }))),
    shareReplay(1)
  );

  constructor(
    private objectSvc: ObjectService
  ) { }

  ngOnInit() {

    funcToAdd = (response: any) => {
      let data = JSON.parse(response);
      data.forEach(x => {
        let obj: NewObject = { name: x.name, title: alterString(x.title) }

        // Trying to add the obj object into the existing object array Observable here
      });
    };

    funcToDelete = (response: any) => {
      let data = JSON.parse(response);
      data.forEach(x => {
        let obj: NewObject = { name: x.name, title: alterString(x.title) }

        // Attempting to delete the obj object from the current object array Observable here
      });
    };

  }
}

This is my example.component.html:

<div *ngFor="let o of objects$ | async">
   <p>{{ o.name }}</p>
   <p>{{ o.title}}</p>
</div>

Here's my service object.service.ts:

import { Injectable } from '@angular/core';
import { Observable, throwError, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { ClientApi, ApiException, NewObject } from '../client-api.service';

@Injectable({
  providedIn: 'root'
})
export class ObjectService {

  constructor(
    private clientApi: ClientApi
  ) { }

  getAllObjects(name: string): Observable<NewObject[]> {
    return this.clientApi.getAllObjects(name)
      .pipe(
        map((x) => x.result),
        catchError(err => {
          if (ApiException.isApiException(err)) {
            if (err.status === 404) {
              return of<NewObject[]>(undefined);
            }
          }
          return throwError(err);
        })
      );
  }
}

Once the JSON response has been formatted, the objective is to insert the obj object into the objects$ Observable and display it in the UI.

A suggestion has been made to utilize a BehaviorSubject element. Any guidance on how this can be accomplished smoothly would be much appreciated.

Answer №1

To send out results generated by the `somethingThatHappens()` function, you can utilize a `BehaviorSubject` and then use `combineLatest()` to combine these results with those fetched from the service.

objectsFromService$ = this.objectSvc.getAllObjects("objects").pipe(
    map(obj => obj.map(x => ({ name: x.name, title: alterString(x.title) })))
);

resultsArray = []
resultsSubject = new BehaviorSubject(this.resultsArray);
results$ = this.resultsSubject.asObservable()

readonly objects$: Observable<NewObject[]> = combineLatest(
    this.results$,
    this.objectsFromService$
).pipe(
   map(([results, objects]) => ([...results, ...objects])),
   shareReplay(1)
)


somethingThatHappens = (response: any) => {
      let data = JSON.parse(response);
      data.forEach(x => {
        let obj: NewObject = { name: x.name, title: alterString(x.title) }
        this.resultsArray.push(obj)          
      });
      this.resultsSubject.next(this.resultsArray)
};

I have demonstrated the implementation in a functional stackblitz project here: https://stackblitz.com/edit/angular-jnngwh

Hopefully, this helps clarify things for you.

Answer №2

It seems that Harun provided a solid answer ()

If you only need to update this observable once, such as during initialization, you can use a straightforward approach like the following:

export class ExampleComponent implements OnInit {

    let objects$: Observable<NewObject[]>;

    constructor(
        private objectSvc: ObjectService
    ) { }

    private initializeObservable() {
        return this.objectSvc.getAllObjects("objects").pipe(
            map(obj => obj.map(x => ({ name: x.name, title: alterString(x.title) }))),
            shareReplay(1)
        );
    }

    private getObservableWithNewValues() {
        const response = callService();
        let array = [];
        let data = JSON.parse(response);
        data.forEach(x => {
            let obj: NewObject = { name: x.name, title: alterString(x.title) }
            array.push(obj);
        });

        // This creates an observable of the array
        return of(array);
    }

    ngOnInit() {
        const initialObservable = this.initializeObservable();

        const anotherObservable = this.getObservableWithNewValues();

        this.objects$ = concat(initialObservable, anotherObservable);
    }
}

For further reference:

Answer №3

Exploring two different approaches to tackle this problem.

Approach 1: https://stackblitz.com/edit/angular-rajvqq

constructor(private myService: MyService){
  this.myService.getObjects().subscribe(x => this.somethingThatHappensToAdd(x));
}

resultsArray = []
resultsSubject = new BehaviorSubject(this.resultsArray);

readonly objects$: Observable<number[]> = combineLatest(
  this.resultsSubject,
).pipe(
  map(([results]) => ([...results])),
  shareReplay(1)
)

somethingThatHappensToAdd( arrayOfValues ) {
  arrayOfValues.forEach(x => {
    this.resultsArray.push(x)          
  });
  this.resultsSubject.next(this.resultsArray)
};

delete(o: any) {

  // This is the same as just accessing this.resultsArray
  let newArray: any[] = this.resultsSubject.getValue();
  for (let i = newArray.length - 1; i >= 0; i--) {
    if (newArray[i] == o) {
      newArray.splice(i, 1);
    }
  }

  this.resultsSubject.next(newArray);
}

Approach 2: https://stackblitz.com/edit/angular-gxfk16

deleteSubject = new Subject<any>();
addSubject = new Subject<any[]>();

objects$ = this.myService.getObjects().pipe(
  switchMap(x => merge(
    of(x), 
    this.deleteSubject.pipe(map(y => this.deleteAll(y, x))),
    this.addSubject.pipe(map(y => { x.push(...y); return x; }))
  )),
  shareReplay(1)
);
constructor(private myService: MyService){ }

deleteAll(o: any, v: any[]) {
  for (let i = v.length - 1; i >= 0; i--) {
    if (v[i] == o) {
      v.splice(i, 1);
    }
  }
  return v;
}

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

Error in Typescript for a function that accepts several types of lists - property does not exist on the interface

In my project, I have defined three interfaces: a base interface and two others that extend the base one with children as properties. One of the interfaces includes 'valueType' while the other includes 'returnType'. Here is how they are ...

Is it possible to enable full screen window functionality in Angular 2 by simply clicking a button? Let's find out

After successfully creating the user login page, I am facing an issue. When the submit button is clicked, the page should navigate to a specific component (test.component.ts and test.component.html). My goal now is to make that window go into full screen m ...

Vue's span function is yielding the promise object

In my Vue component, I am using the function getOrderCount to fetch the number of orders from a specific URL and display it in one of the table columns. <div v-html="getOrderCount(user.orders_url)"></div> async getOrderCount(link) { ...

Acquiring and resetting Angular states: A beginner's guide

I'm facing a situation where I need to perform a route jump (essentially a refresh) on the same page, but this jump includes state data. However, even though the state contains valuable information, I am only able to access it through history and cann ...

What is the process of associating components with a specific tag in Angular?

Here is the structure of my angular-electron application that I'm currently working on. Take a look at it here. Within the dynamic area or tag, I aim to bind various components based on the selection of list items in the side-nav component. These com ...

Creating a dynamic list in Typescript from a tuple array without any intersections

const colors = ["red", "blue", "green", "yellow"] as const; const buttonSizes = ["small", "medium", "large"] as const; type ColorType = (typeof colors)[number]; type SizeType = (typeof b ...

When accessing an Angular 7 page directly through the URL in the browser, it becomes unresponsive. However, the page works perfectly fine when navigating

I've been tackling a poorly developed Angular 7 legacy application and encountering a bizarre issue. There's a component that requires a parameter for email verification, but when the URL is visited directly, it doesn't function as expected. ...

Combining TypeScript and ReactJS with RequireJS: A guide to importing react-dom in TypeScript

I am currently working with TypeScript and React.js. Within Visual Studio 2015, I have set the project properties to use AMD as the module system for TypeScript build, meaning I am utilizing requirejs for importing modules. Within my main.tsx file, I am r ...

When {} = {} is utilized in an Angular constructor, what is its function?

While going through an Angular dynamic forms tutorial, I came across this code snippet and got confused by the {} = {} in the constructor. Here is the complete snippet: export class QuestionBase<T> { value: T; key: string; label: string; re ...

Discover the inverse of Object Arrays interaction in TypeScript

My agent object has the following structure: agentObj = { "agentId": "saqib", "attributes": [ { "name": "Marketing", "type": "Boolean", }, { "name": "English", "type": "Profi ...

Error: The 'Store' property is not found in the '{}' type but is needed in the 'Readonly<Istore>' type. TS2741

Can you assist me in resolving this issue? I am attempting to pass my store as props to the component, but I keep encountering the following error: Type error: Property 'Store' is missing in type '{}' but required in type 'Readon ...

Zod: ensure at least one field meets the necessary criteria

Currently, I am in the process of developing a form that allows users to input either their email address or phone number. While they have the option to provide both, they are required to enter both before proceeding. For this project, I am utilizing Zod a ...

What is the reason behind TypeScript's lack of inference for function parameter types when they are passed to a typed function?

Check out the code snippets below: function functionA(x: string, y: number, z: SpecialType): void { } const functionWrapper: (x, y, z) => functionA(x, y, z); The parameters of functionWrapper are currently assigned the type any. Is there a way we can ...

Verify if the Contact Number and Email provided are legitimate

Is it possible to determine if the phone number or email entered by a user is valid or not? Using Angular 7 and Firebase? ...

Steps to deactivating a styled button using React's styled-components:

I've created a very basic styled-components button as follows: import styled from 'styled-components'; const StyledButton = styled.button``; export const Button = () => { return <StyledButton>Default label</StyledButton> ...

The server has access to an environment variable that is not available on the client, despite being properly prefixed

In my project, I have a file named .env.local that contains three variables: NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY=pk_test_<get-your-own> MAGIC_SECRET_KEY=sk_test_<get-your-own> TOKEN_SECRET=some-secret These variables are printed out in the file ...

Encountering an unusual behavior with React form when using this.handleChange method in conjunction

RESOLVED I've encountered a quirky issue with my React/Typescript app. It involves a form used for editing movie details fetched from a Mongo database on a website. Everything functions smoothly except for one peculiar behavior related to the movie t ...

Determining the type inference in Typescript based on column data objects

Within my object that describes my table, I have a property called dataFields, which is an array of data with 3 keys - name (a required string), label (a required string), and field (an optional string). Now, I would like to add another property called tes ...

What are the steps to display 10 items sequentially with the angular2-infinite-scroll package?

I'm currently working with the angular 2 infinite scroll module and I need to display 10 elements at a time. When the user scrolls down, the next 10 elements should be shown and the scrollbar should adjust accordingly. I've tried several methods ...

How to showcase ByteArrayContent using Angular

I have a Web API that has been functioning well for two years in existing Knockout and polymer applications, so I don't want to make any changes to it. Now, I am trying to integrate Angular and need to display an image from the API. Below is the code ...