Search code examples
angulartypescriptrxjsobservablebehaviorsubject

Adding/Deleting an object to an Obervable of an object array in Angular/RXJS?


Goal: To add an object to an existing Observable array of objects. Having this reflect on the DOM is 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 {

  // Initializing the object array Observable 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() {

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

        // Here's where I'm trying to add the obj object into the already existing object array Observable
      });
    };

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

        // Here's where I'm trying to delete the obj object from the already existing object array Observable
      });
    };

  }
}

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);
        })
      );
  }
}

After formatting of the response in JSON, I want to be able to insert the obj object into objects$ Observable and have it reflect on the UI.

I am advised to use a BehaviorSubject element to make this happen. Can anyone advise on how this can be easily done?


Solution

  • I found two ways to go about this solution.

    Solution 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);
    }
    

    Solution 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;
    }