17

There is an Observable of the array of places:

places: Observable<Array<any>>;

In template it used with the async pipe:

<tr *ngFor="let place of places | async">
  ...
</tr>

After some user actions I need to remove the place with specific id from this array. I have something like this in my code, but it doesn't work:

deletePlace(placeId: number): void {
  this.apiService.deletePlace(placeId)
  .subscribe(
    (res: any) => {
      this.places
        .flatMap((places) => places)
        .filter((place) => place.id != placeId);
    }, 
    (err: any) => console.log(err)
  );    
}  

Can you help me with this?

daslashi
  • 357
  • 1
  • 3
  • 13

4 Answers4

23

You can't do it this way since you can't "update" an observable (i.e. it doesn't keep states) but you can react to an event through it.

For your use case, I would leverage the scan operator and merge two streams into a single one:

  • one for the initial loading
  • another one for the delete event.

Here is a sample:

let obs = this.http.get('/data').map(res => res.json());

this.deleteSubject = new Subject();

this.mergedObs = obs.merge(this.deleteSubject)
.startWith([])
.scan((acc, val) => {
  if (val.op && val.op==='delete') {
    var index = acc.findIndex((elt) => elt.id === val.id);
    acc.splice(index, 1);
    return acc;
  } else {
    return acc.concat(val);
  }
});

To trigger an element deletion, simply send an event on the subject:

this.deleteSubject.next({op:'delete', id: '1'});

See this plunkr: https://plnkr.co/edit/8bYoyDiwM8pM74BYe8SI?p=preview.

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
3

You can take advantage of filter operator:

this.places$
        .pipe(
            map(places => {
                // Here goes some condition, apply it to your use case, the condition only will return when condition matches
                return places.filter(place => place.placeId !== 0);
            }),
            map(response => (this.users$ = of(response)))
        )
        .subscribe(result => console.warn('Result: ', result));
Tecayehuatl
  • 172
  • 1
  • 10
  • @kushalBaldev, regardless the template update this will return a Observable; if you have a binding in your template this will be automatically updated since there is a sync between your observable and the template. – Tecayehuatl Oct 14 '20 at 16:02
1

The filter function is immutable and won't change the original array.

I would change the deletePlace function to something like this:-

deletePlace(placeId: number):  void {
  this.apiService.deletePlace(placeId)
  .subscribe(
    (res: any) => {
      this.places = this.places.filter((place) => place.id != placeId);
    }, 
    (err: any) => console.log(err)
  );    
}  
Krishnanunni Jeevan
  • 1,719
  • 1
  • 15
  • 24
1

RxJS version 6

Using the accepted answer with RxJS 6 and typescript will throw an error because the observables hold different types. you better use combineLatest, you could also use zip but it will not work! did you just ask why? the answer is here :)

combineLatest([
  this.items$,
  this.deleteItem$
]).pipe(
  takeUntil(this.onDestroy),
  tap(([items, deleteItem]) => {
    if (deleteItem && deleteItem.op === 'deleteItem') {
      var index = items.findIndex((item) => item.id === deleteItem.id);
      if (index >= 0) {
        items.splice(index, 1);
      }
      return items;
    }
    else {
      return items.concat(deleteItem);
    }
  })
).subscribe();

then you can send the event..

this.deleteItem$.next({ op: 'deleteItem', id: '5e88fce485905976daa27b8b' });

I hope it will help someone..

user12163165
  • 555
  • 8
  • 12
  • 1
    The problem with this solution is, that combineLatest always emit the latest value of deleteItem$ as well. Means, after deleteItem$ emits the first time, the script will try to delete this item every time items$ emit. – schaenk Jul 08 '20 at 08:15