Konrad Viltersten
Konrad Viltersten

Reputation: 39148

How to add element to a list when it's async loaded from service via observable in Angular?

In my page, I have two lists of ngFor'ed objects. One list is the assigned members, the other is assignable members. Both are shown by the following pattern.

<ng-container *ngIf="available$ | async as available">
  <div *ngFor="let item of available">
    <span (click)="assign(item)"</span>{{item.name}}
  </div>
</ng-container>

this.available$ = this.service.getMembers(false)
    .pipe(
      map(__ => ...), ... );

When I click on the assignable member, I want it to disappear from the list and appear in the other. I wonder how to go about achieving that without running subscribe(...) explicitly and assigning to the lists.

One way, would be saving the stuff to the server, then reloading the data but it seems like a huge overkill. another option is to have local, non-observable lists and managed them. However, I kind of like the async pipe and would prefer to keep the component code to the minimum. (Also, I have the whole loading/failure GUI set up based on the pipe in the page so it would be quite some work to rewrite.)

Upvotes: 2

Views: 1741

Answers (2)

Ivan Mihaylov
Ivan Mihaylov

Reputation: 433

As far as I understand, what you want is to combine your service observable with the output from your assign function without having to use subscribe. I would suggest that you create a subject $newMember in your class that would emit the value of the new member you have as a result of your assign function. Thereafter you can do something like

this.available$ = combineLatest(this.service.getMembers(false), newMember$).pipe(map(...));

In the demo you can find the following:

    this.repos = combineLatest(
  this.http.get(this.path),
  this.itemObs.asObservable()
).pipe(
  map(([req, action]) => {
    switch (action.operation) {
      case "add": {

        return;
      }
      case "remove": {
        this.newReps = this.newReps.filter(item => item.id !== action.id);
        return this.newReps;
      }
      default: {
        this.newReps = req["items"];
        return this.newReps;
      }
    }
  })
);

and your subscription will stay the same using the async pipe.

Here is a Stackblitz demo

You need to have a look at the rxjs documentation now if you are wondering about the different operators.

merge - The output Observable only completes once all input Observables have completed. You will have to retrigger your request every time.

combineLatest - Will emit a projection of the latest values for ALL inputs every time there is a new value for one of the inputs.

Moreover there are at least 15 operators that do this kind of merging of two or more streams with a slightly different behaviours. You just have to make sure to pick the one that fits best your case.

Upvotes: 2

dev hedgehog
dev hedgehog

Reputation: 8791

not sure if I get your question right but let me give it a try with pseudocode

import {merge, of, from, ...} from 'rxjs/operators';

assign(item){
 this.assigned = merge(this.assigned, of(item)); // add to list
 this.available = this.available.pipe(map(x => x.filter(y => y.id != item.id)); //remove from list
}

In case you might want to implement combileLatest solution provided by Ivan here is some pseudo code how to filter out deleted item from its list:

this.available$ = this.service.getMembers(false)
    .pipe(
      map(__ => ...),
      combineLatest(newMember.pipe(map(delItem => {action:"del", item = delItem}))
      filter(x => x[action] != 'del')));

something like this?

Upvotes: 3

Related Questions