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

The absence of a template or render function in a Vue.js 3 and Quasar 2 component has resulted in an

I am currently working on creating a dynamic component and passing a prop to it. However, I am encountering a warning message that says: Component is missing template or render function. Although the component is being rendered, I am still receiving the wa ...

Chaining Assignments in TypeScript

let a: { m?: string }; let b = a = {}; b.m = ''; // Property 'm' does not exist on type '{}'. let a: { m?: string } = {}; let b = a; b.m = ''; // It's OK Link to TypeScript Playground What occurs ...

Managing errors in ErrorHandler and addressing them in HttpInterceptor

Can you explain the difference between error handling methods in Angular 7? Is it necessary to handle global errors in HttpInterceptor and also in Angular's built-in ErrorHandler? In the HttpInterceptor, what types of errors can be handled, and in the ...

Tips for adjusting the zIndex of the temporary drawer component in TypeScript

Currently, I am working on incorporating a "temporary" drawer that is clipped under the app bar. Please note that the drawer variant is set to 'temporary' and it needs to be clipped under the app bar. If you need more information, refer to my pr ...

Discover a more efficient method for expanding multiple interfaces

Hey there, I'm having some trouble with TypeScript and generics. Is there a better way to structure the following code for optimal cleanliness and efficiency? export interface Fruit { colour: string; age: number; edible: boolean; } export inte ...

What causes the variable to be undefined in the method but not in the constructor in Typescript?

I am currently working on an application using AngularJS 1.4.9 with Typescript. In one of my controllers, I have injected the testManagementService service. The issue I'm facing is that while the testManagementService variable is defined as an object ...

Creating a function in Typescript to extend a generic builder type with new methods

Looking to address the warnings associated with buildChainableHTML. Check out this TS Playground Link Is there a way to both: a) Address the language server's concerns without resorting to workarounds (such as !,as, ?)? b) Dodge using type HTMLChain ...

Exploring how NestJS can serialize bigint parameters within DTOs

I have data transfer objects (DTOs) with parameters that are of type bigint. However, when I receive these DTOs, the parameters always have a type of string. Here is an example: @Get("") async foo(@Query() query: Foo) { console.log(typeof Foo ...

Configuring a NestJS application to establish a TypeOrm connection using environment variables and @nestjs/config

Looking for the best way to set up a NestJS database using a .env file in compliance with legal requirements. The goal is to utilize the @nestjs/config package to import .env variables and incorporate them into the TypeOrmModule. It appears that utilizing ...

Error encountered when attempting to utilize Path Aliases in Angular 11.tsconfig

Currently, I am working on a project using Angular 11 and aiming to utilize short imports like import {smthg} from '@common' instead of import {smthg} from '../../../common' However, I keep encountering errors in IDEA: TS2307: Cannot f ...

There is no such property - Axios and TypeScript

I am attempting to retrieve data from a Google spreadsheet using axios in Vue3 & TypeScript for the first time. This is my initial experience with Vue3, as opposed to Vue2. Upon running the code, I encountered this error: Property 'items' does ...

The types 'X' and 'string' do not intersect

I have a situation where I am using the following type: export type AutocompleteChangeReason = | 'createOption' | 'selectOption' | 'removeOption' | 'clear' | 'blur'; But when I try to compress the cod ...

Issues with Next.js and Framer Motion

My component is throwing an error related to framer-motion. What could be causing this issue? Server Error Error: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function This error occurred during page generation. Any console logs will be ...

Ways to verify and incorporate https:// in a URL for a MEAN Stack application

When extracting the URL from API data, my code looks like this: <div class="row copy-text"> <a href="{{copy.Url}}" target="_blank" style="text-decoration: underline !important;">{{copy.Title}}</a> </div> I am interested in ve ...

Tips for using a loop variable as an argument in a method call for an attribute reference

Within the html code of my component, I have the following: <div *ngFor="let image of images; let i=index;" class="m-image-wrapper"> <i class="fa fa-times m-delete-img" (click)="removeImage(i, {{image.docname ...

Is there a way to receive messages from background.js of a Chrome Extension within an Angular web application?

I've developed a Chrome Extension that sends messages to all tabs (through background.js) using the following code: chrome.tabs.query({}).then((tabs)=> { if (tabs) { tabs.forEach(tab => { chrome.tabs.sendM ...

The property 'filter' is not recognized on the 'Object' type. An attempt to filter the response was made

Trying to fetch data from a JSON file that matches the player's name in the URL, such as localhost:4200/players/Febiven, should only retrieve information about Febiven. The code is written in Angular 6. The current code snippet is as follows: player ...

You cannot set parameters using Angular's httpHeaders post method

Seeking assistance in obtaining the authtoken from RestAPI using a specific method. While the same URL, Parameters, and Headers provide the correct response in Postman, I am encountering a 401 unauthorized error in Angular. Can someone help me identify whe ...

I keep encountering the ExpressionChangedAfterItHasBeenCheckedError error in Angular, even though I'm calling it before the view is checked. What could

This is a piece of code that I have been working on home-component.ts export class HomeComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked { loading = false; constructor() { } ngOn ...

Whenever I execute the 'ng serve' command, I encounter an issue with ineffective mark-compacts close to the heap limit, resulting in an allocation failure and a JavaScript

I'm currently using Angular 9 and Node.js 12. When I input ng serve, I encounter the following problem: C:\Users\homz\my-app>ng serve 93% after chunk asset optimization SourceMapDevToolPlugin vendor.js generate SourceMap <--- ...