1

I'm sending a request to the server to modify one of the users.

As a response, the server sends back the updated user.

After that I'm trying to update the BehaviorSubject of all the users like this:

   private currentHeroes = new BehaviorSubject<Hero[]>(null);
   currentHeroes$ = this.currentHeroes.asObservable();


 powerUp(id: number) {
return this.http
  .post<Hero>(environment.apiUrl + 'heroes/powerUp/' + id, {})
  .pipe(
    tap((updatedHero: Hero) => {
      this.currentHeroes.next(
        this.currentHeroes.value.map((hero: Hero) =>
          hero.id === updatedHero.id ? updatedHero : hero
        ).sort(a=>a.currentPower)
      );
    })
  );

}

In case of deleting:

    delete(id: number) {
return this.http.delete<Hero>(environment.apiUrl + 'heroes/' + id).pipe(
  tap((deletedHero: Hero) => {
    this.currentHeroes.value.filter(
      (hero: Hero) => hero.id !== deletedHero.id
    );
  })
);

}

I'm trying to modify the array of users. Replace the old user with the updated user.

And the code doesn't reach the pipe of the currentHeroes$. I'm not really sure why is that happening or how can I fix it.

I have tried to return a value from the pipe or use tap instead of map but no luck.

Does any can maybe take a look to explain to me why does it happen? or how can I fix it?

Thanks!

John
  • 81
  • 1
  • 2
  • 10

1 Answers1

6

Updating an object in array

Variant 1: Using value getter of BehaviorSubject

You don't have to pipe to the BehaviorSubject. You'd just need to use the tap operator on the source observable (HTTP request) and update the BehaviorSubject in it's callback. tap operator is akin to map but wouldn't transform the incoming emissions. So it's ideal for performing side-effects like updating a local variable.

import { tap } from 'rxjs/operators';

powerUp(id: number) {
  return this.http.post<Hero>(environment.apiUrl + 'heroes/powerUp/' + id, {}).pipe(
    tap((updatedHero: any) => {
        this.currentHeroes.next(
          this.currenHeroes.value.map((hero: Hero) =>        // <-- `Array#map` function
            hero.id === updatedHero.id ? updatedHero : hero
          )
        );
      }
    })
  );
}

Variant 2: Using withLatestFrom operator

If you do not wish to use the value getter from the BehaviorSubject, you could try to use the withLatestFrom operator to fetch it's latest value.

import { tap, withLatestFrom } from 'rxjs/operators';

powerUp(id: number) {
  return this.http.post<Hero>(environment.apiUrl + 'heroes/powerUp/' + id, {}).pipe(
    withLatestFrom(this.currentHeroes$),
    tap(([updatedHero, currentHeroes]) => {
        this.currentHeroes.next(
          currenHeroes.map((hero: Hero) => 
            hero.id === updatedHero.id ? updatedHero : hero
          )
        );
      }
    })
  );
}

Deleting an object in array

As mentioned in the comments, to delete an element in the array, you could use the Array#filter instead of Array#map function.

Variant 1: Using value getter of BehaviorSubject

import { tap, withLatestFrom } from 'rxjs/operators';

powerUp(id: number) {
  return this.http.delete<Hero>(environment.apiUrl + 'heroes/' + id).pipe(
    tap(([deletedHero, currentHeroes]) => {
        this.currentHeroes.next(
          this.currentHeroes.value.filter((hero: Hero) => hero.id !== deletedHero.id)
        );
      }
    })
  );
}

Variant 2: Using withLatestFrom operator

import { tap, withLatestFrom } from 'rxjs/operators';

powerUp(id: number) {
  return this.http.delete<Hero>(environment.apiUrl + 'heroes/' + id).pipe(
    withLatestFrom(this.currentHeroes$),
    tap(([deletedHero, currentHeroes]) => {
        this.currentHeroes.next(
          currentHeroes.filter((hero: Hero) => hero.id !== deletedHero.id)
        );
      }
    })
  );
}
ruth
  • 29,535
  • 4
  • 30
  • 57
  • But actually i have another question. Thanks to you I understood my mistake! But in case of deleting a hero. ----> hero.id === deletedHero.id ? {} : hero; doesn't work. did I miss anything? because just {} doesn't work as I thought it would – John Nov 29 '21 at 15:07
  • I have edited my qeustion so you can see how can I try to delete a hero – John Nov 29 '21 at 15:10
  • No in that case you'd have an empty object in the array. For removing the object, you could use `Array#filter`: `currenHeroes.filter((hero: Hero) => hero.id !== deletedHero.id)`. – ruth Nov 29 '21 at 15:11
  • unfortently i have tried and it says : Property 'filter' does not exist on type 'BehaviorSubject'.ts(2339) – John Nov 29 '21 at 15:13
  • Yes because this is the 2nd variant of the answer. For this to work you'd need to use `withLatestFrom` operator. Or you could just use the `value` getter: `this.currentHeroes.value.filter((hero: Hero) => hero.id !== deletedHero.id)` – ruth Nov 29 '21 at 15:16
  • yeah. btw i have added the delete method so you can see. I don't really understand why it doesn't work. (The second method works but I really want to understand how to make the first one work as well) – John Nov 29 '21 at 15:20
  • @David: I've updated the answer for updating and deleting elements from array using both the variants. To understand why your solution didn't, I'd recommend reading up on the `value` getter of the `BehaviorSubject` and the RxJS `withLatestFrom` operator. – ruth Nov 29 '21 at 15:25
  • Thanks! One last question please, Could you please explain to me in a few words what does withLatestFrom do? As i understand it takes the latest value from the BehaviorSubject. right ? – John Nov 29 '21 at 15:33
  • @David: Yes exactly and not just `BehaviorSubject`. It could take the latest value from any observable and include it in the emission stream as an additional element to an array. We were obtaining these elements using the spread syntax `([deletedHero, currentHeroes])`. The first element is the emission from the HTTP request, the second is the emission from the `withLatestFrom` operator. – ruth Nov 29 '21 at 15:36
  • I got it! Thanks for the amazing explanation!!! – John Dec 01 '21 at 16:14
  • Last thing, is it possible to apply few filters together? I mean to sort by integer? – John Dec 01 '21 at 16:15
  • I tried to use the .sort() after the map but with no success. i Have edited the question could you please take a look? – John Dec 01 '21 at 16:35
  • @David: You're using the `Array#sort` function wrong. It must be `.sort((a,b) => (a.currentPower > b.currentPower) ? 1 : ((b.currentPower > a.currentPower) ? -1 : 0))`. See here: https://stackoverflow.com/a/1129270/6513921 – ruth Dec 02 '21 at 08:04
  • Generally speaking, its possible to make few operations on .value()? Lets say .map() and after it .sort() ect? – John Dec 02 '21 at 08:37
  • @David: If the value held by the `BehaviorSubject` is an array, then yes. – ruth Dec 02 '21 at 08:48
  • Thank you so much! Sorry for asking this question so late. Is it possible to apply this logic to a Subject and not BehaviorSubject – John Dec 08 '21 at 15:12