Search code examples
angulartypescriptobservableangular14

Replace custom object in an observable array based on matching object property


So I'm fetching an observable array of custom IPix objects (Observable<IPix[]>) from a db (via an API) and then updating a record in the db by passing an edited copy of the IPix object back to the API in a PUT request (based on legacyConfigTrackerId).

I want to know how to replace the original IPix object in the array currently being displayed with the altered copy (based on legacyConfigTrackerId) so that the changes to the record will be reflected in the table right away without having to search/refresh.

Right now I can't even figure out how to filter the array, the result of the filter keeps coming back undefined and then nothing is displayed:

  pixRecords$!: Observable<IPix[]>;

  updatePix(pix: IPix) {
    console.log('update clicked');

    this.pixRecords$ = 
      this.pixRecords$.pipe(
        map((records: IPix[]) =>
          records.filter((p: IPix) => {
            p.legacyConfigTrackerId !== pix.legacyConfigTrackerId 

            // need to do something to replace original object in array with the 'pix' argument
          })
        )
      );

    pix.contract.effectiveDate = this.revertEffDateFormat();
    pix.contract.termDate = this.revertTermDateFormat();
    this.pixService.putPix(pix);
    this.formattedEffDate = '';
    this.formattedTermDate = '';
    this.closeModal.next(null);
  }

I'm new to Angular so maybe my approach is all wrong, I'm open to suggestions.


Solution

  • When you are working with Observables, values are emitted and then gone. You can't treat a variable holding an Observable like other variables. You shouldn't really assign it to something (like this: this.pixRecords$ = this.pixRecords$.pipe(...)).

    You instead need to either:

    • Store the emitted data in an array that you work with in the code. (This is often the easiest approach.)
    • Or leverage features of Observables to retain the data and work with it in an Observable pipeline.

    For the second option, there are several approaches:

    • You can use the scan operator to retain the emitted array of data and add/remove/update the values in the array.
    • You can use a BehaviorSubject and emit the returned array from the http call into a BehaviorSubject, which retains it's last emission.

    Here is an example of using the scan operator:

      products$ = this.http.get<Product[]>(this.productsUrl)
        .pipe(
          tap(data => console.log('Products: ', JSON.stringify(data))),
          catchError(this.handleError)
        );
    
      private productInsertedSubject = new Subject<Product>();
      productInsertedAction$ = this.productInsertedSubject.asObservable();
    
      allProducts$ = merge(
        this.products$,
        this.productInsertedAction$
      ).pipe(
        scan((acc, value) =>
          (value instanceof Array) ? [...value] : [...acc, value], [] as Product[])
      )
    

    Here the retrieved products are emitted into the products$ Observable. But the UI binding will be to the allProducts Observable.

    The productInsertedAction emits any newly added product. The addProducts$ Observable then re-emits any time a new product is added.

    The scan operator retains the prior array of products, so this code is able to reference that (as the acc variable) and add to it.

    This code could be expanded to include update and delete operations.

    You mentioned that you were new to Angular. I have an intro to RxJS video that you can find here if you want to dive a bit deeper into the basics: https://youtu.be/vtCDRiG__D4