7

I use angular 6 and I would like to filter the results of an async pipe, before rendering them in the UI.

Here is my code right now

this.results = this.form.get('name').valueChanges.pipe(           
  filter(formdata => formdata.name.length > 0), 
  switchMap( formdata => this.service.getNames(formdata.name)) 
);

and the template

  <div *ngIf='results | async ; let items'>
       <div *ngFor='let item of items'>{{item.id}} {{item.name}} </div>               
  </div> 

From the pipe I get some ids and names. I already have an array of ids. I would like to filter the ids of the pipe and not render the ones that are already in the array.

So, here is what I try to do.

array = [{id:1,name:'one'},{id:2,name:'two'}];//I already have this

new version of filter in pipe

this.results = this.form.get('name').valueChanges.pipe(           
  filter(formdata => formdata.name.length > 0), 
  switchMap( formdata => this.service.getNames(formdata.name)) ,
  filter(checkIfResultIdInArray())//pseudocode
);

Assume that checkIfResultIdInArray is I function I created. Filters and returns all the ids that are not in the array. So the ids/names that end up in the template are not the {id:1,name:'one'},{id:2,name:'two'}.

Or maybe I can filter in the template somehow?

WhatsThePoint
  • 3,395
  • 8
  • 31
  • 53
slevin
  • 4,166
  • 20
  • 69
  • 129
  • 2
    Instead of using the `Observable` filter function, you should use `map` in conjunction with the array `.filter` function instead – user184994 Nov 06 '18 at 22:10
  • rxjs `filter` check if the event must be propagated. array `filter` filter the array (what you want). The rxjs `map` will propagate new value (you want to propagate the new filtered array). So like user184994 said, do an array filter into a rxjs map. https://www.learnrxjs.io/operators/transformation/map.html – Gilsdav Nov 06 '18 at 23:23

3 Answers3

10

@Davy's answer is what I would do myself. However another option is to use a pipe. This is the way to go if you would like to reuse this functionality.

@Pipe({name:'filterOnId'})
export class FilterOnIdPipe implements PipeTransform {
    transform(list : MyObject[], acceptedIds : number[]){
        return list.filter(item => acceptedIds.indexOf(item.id) > -1);
    }
}

and in the template

<div *ngFor='let item of results | async | filterOnId : acceptedIds'>
    {{item.id}} {{item.name}} 
</div>

Note the following:

You use your custom pipe the same way you use built-in pipes. You must include your pipe in the declarations array of the AppModule If you choose to inject your pipe into a class, you must provide it in the providers array of your NgModule.

DevLoverUmar
  • 11,809
  • 11
  • 68
  • 98
Yoeri
  • 2,249
  • 18
  • 33
4

As suggested in the comments, you can replace AsyncPipe by a regular array, or alter the value of the emitted value (@Davy's solution is good).

There is however a template based solution. I'm putting it here for those who don't want to merge component's logic with view display.

component

result$ = of([1,2,3,4,5,6,7,8]); // for the sake of example

isAcceptedThing(thing){
  return thing%2 != 0 // accept only odd numbers
}

template

<ul >
  <ng-container *ngFor="let thing of result$ | async">
    <li *ngIf="isAcceptedThing(thing)">
      filtered thing = {{ thing }} 
    </li>
  </ng-container>
</ul>

Output

  • filtered thing = 1
  • filtered thing = 3
  • filtered thing = 5
  • filtered thing = 7
Yoeri
  • 2,249
  • 18
  • 33
madjaoue
  • 5,104
  • 2
  • 19
  • 31
  • 1
    One can even do this only in template like `
  • `. It may be easier for basic filter operations, but will become so messy for complex operations of course.
  • – MÇT Aug 10 '19 at 11:03