Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter items inside “ngFor” loop, based on object property string

I need to filter items inside an ngFor loop, by changing the category in a drop-down list. Therefore, when a particular category is selected from the list, it should only list the items containing that same category.

HTML Template:

<select>
  <option *ngFor="let model of models">{{model.category}}</option>
</select>

<ul class="models">
  <li *ngFor="let model of models" (click)="gotoDetail(model)">
  <img [src]="model.image"/>
  {{model.name}},{{model.category}}
  </li>
</ul>

Items Array:

export var MODELS: Model[] = [
{ id: 1, 
  name: 'Model 1', 
  image: 'img1', 
  category: 'Cat1', 
},

{ id: 2, 
  name: 'Model 2', 
  image: 'img2', 
  category: 'Cat3',
},

{ id: 3, 
  name: 'Model 3', 
  image: 'img3', 
  category: 'Cat1',
},
{ id: 4, 
  name: 'Model 4', 
  image: 'img4', 
  category: 'Cat4',
},

...
];

Also, the drop-down list contains repeated category names. It is necessary for it to list only unique categories (strings).

I know that creating a custom pipe would be the right way to do this, but I don't know how to write one.

Plunker: http://plnkr.co/edit/tpl:2GZg5pLaPWKrsD2JRted?p=preview

like image 925
gundra Avatar asked Nov 19 '16 18:11

gundra


1 Answers

Here is a sample pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'matchesCategory'
})
export class MathcesCategoryPipe implements PipeTransform {
    transform(items: Array<any>, category: string): Array<any> {
        return items.filter(item => item.category === category);
    }
}

To use it:

<li *ngFor="let model; of models | matchesCategory:model.category" (click)="gotoDetail(model)">

===== for the plunkr example ====

You need your select changes to reflect in some variable

First define in your class a member:

selectedCategory: string;

then update your template:

<select (change)="selectedCategory = $event.target.value">
   <option *ngFor="let model of models ">{{model.category}}</option>
</select>

last, use the pipe:

  <li *ngFor="let model; of models | matchesCategory:selectedCategory" (click)="gotoDetail(model)">

==== comments after seeing the plunker ====

I noticed you used promise. Angular2 is more rxjs oriented. So the first thing I'd change is in your service, replace:

getModels(): Promise<Model[]> {
  return Promise.resolve(MODELS);
}

to:

getModels(): Observable<Array<Model>> {
  return Promise.resolve(MODELS);
}

and

getModels(id: number): Observable<Model> {
  return getModels().map(models => models.find(model.id === id);
}

then in your ModelsComponent

models$: Observable<Array<Model>> = svc.getModels();
uniqueCategories$: Observable<Array<Model>> = this.models$
  .map(models => models.map(model => model.category)
  .map(categories => Array.from(new Set(categories)));

Your options will become:

     <option *ngFor="let category; of uniqueCategories$ | async">{{model.category}}</option>

and your list:

      <li *ngFor="let model; of models$ | async | matchesCategory:selectedCategory" (click)="gotoDetail(model)">

This is a very drafty solution since you have many duplicates and you keep querying the service. Take this as a starting point and query the service only once, then derive specific values from the result you got.

If you'd like to keep you code, just implement a UniqueValuesPipe, its transform will get a single parameter and filter it to return unique categories using the Array.from(new Set(...)). You will need though to map it to strings (categories) first.

like image 170
Meir Avatar answered Sep 20 '22 07:09

Meir