13

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

3
  • 1
    Why not do this filtering in the component code? You could do e.g. *ngFor="let model of filteredModels" and write get filteredModels() { ... } to provide it. You could do the same thing to give an array of unique entries for the select element. Commented Nov 19, 2016 at 18:25
  • "I know that creating a custom pipe would be the right way to do this" - what led you to that conclusion? "but I don't know how to write one" - then have you considered learning? Commented Nov 19, 2016 at 18:28
  • 3
    Sure jonrsharpe :). I'm a novice at this and I hope that I will find the right way. I read a lot of posts about filtering the angular 2, custom pipes are mainly referred as the right choice .. Commented Nov 19, 2016 at 18:35

1 Answer 1

23

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.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks Meir, i'll try to implement this code into my project, to see if it gives result. I will get back to you... thx
Meir, when i tried to filter items by using this code, console throws me an error "Cannot read property 'category' of undefined"
add a break point (or console.log(items, category) at the top of the function and see it gets the values right
and if this doesn't work, put your full implementation on plnkr so I can have a look
you've got a typo in MathcesCategoryPipe (Mathces)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.