1
votes

I'm looking for a filtering solution that will live filter a repeated set of elements. I have a basic pipe solution found in the answers here.

What I've found is that <input [(ngModel)]='query'/> will only work if it's within the same component.

However - I need to have the filter value coming from pre populated radio buttons within another component.

Here is my code so far.

filter.component

<div class="topic" *ngFor="let topic of topics">
  {{topic.term}}
  <input [(ngModel)]="query" type="radio" name="topicFilter" [value]="topic.term"/>
</div>

grid.component

<router-outlet></router-outlet> // this pulls in the filter.component

<input [(ngModel)]="query"> // this is a text input that works as wanted ( for showing what I'm wanting to achieve )

<grid-item *ngFor="let user of users | topicFilterPipe: 'topic':query">
  <a [routerLink]="['user-story', user.uid]">
    <h1>{{user.fname}}</h1>
  </a>
  {{user.topic}}
</grid-item>

filter.pipe

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

@Pipe({
  name: 'topicFilterPipe'
})
export class TopicFilterPipePipe implements PipeTransform {

  public transform(value: Array<any>, keys: string, term: string) {
    console.log('pipe has run');
    if (!term) return value;
    return (value || []).filter((item) => keys.split(',').some(key => item.hasOwnProperty(key) && new RegExp(term, 'gi').test(item[key])));

  }

}

Basically - trying to implement the same method that the text input does, but with the selected radio that is within the filter.component. However I'm struggling to pass the selected value into the pipe & then filter using ngModel. Thank you!

NOTE: I'm VERY new to angular2 + The filter component must remain in a seperate component

2
It is normally not a good idea to use a pipe for filtering if you have more than a few rows of data. See this: angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe You can instead filter in the component itself. - DeborahK
@DeborahK I have at minimum 500 rows of data. So you're probably correct. How would I handle the filtering in filter.component & pass it into the grid.component - Towelie
See my response in an answer below. - DeborahK
As others have mentioned in their answers, a service is a good way to manage state information amongst several components. That is what you're actually doing, changing the state of the application. This service could be extended to communicate information from other filters as well, but the purpose should remain cohesive. e.g. A service to manage UX preferences and another to manage an ad stream that user sees. - Eric Lease

2 Answers

1
votes

You could build a service with a method like this:

performFilter(filterBy: string): IProduct[] {
    filterBy = filterBy.toLocaleLowerCase();
    return this.products.filter((product: IProduct) =>
          product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1);
}

This is filtering a list of products ... but you could tailor it to filter whatever you need. And since this would need to be in a service so it could be shared, you will most likely need to pass in the list to be filtered. So more like this:

performFilter(products: IProduct[], filterBy: string): IProduct[] {
    filterBy = filterBy.toLocaleLowerCase();
    return products.filter((product: IProduct) =>
          product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1);
}
1
votes

Your problem is about communication between components. FilterComponent needs to inform GridComponent that a new query is updated.

Since Service is a singleton in Angular(exceptions exist but not relevant here) if we set the data in service from one component other component can access the data. We are using Observer pattern by using BehaviorSubject to store info.

QueryService :

This service will have the query data. getQuery will give an observable which can be listened to by any component.

@Injectable()
export class QueryService{

    public query = new BehaviorSubject<string>('');

    public getQuery(){
        return query;
    }

    public setQuery(queryStr){
        this.query.next(queryStr);
    }
}

FilterComponent

FilterComponent will call the service and update when value changes.

HTML

<input 
    [ngModel]="query"
    (ngModelChange)="updateQuery($event)" 
    type="radio"
    name="topicFilter" [value]="topic.term"/>

TS

constructor(private queryService: QueryService){}
public updateQuery(query){
    queryService.setQuery(query);
}

GridComponent

public query:string; 

constructor(private queryService: QueryService){}

ngOnInit(){
  // .... other code
  queryService.getQuery()
     .subscribe(queryVal => {
          this.query = queryVal;
     });

}

OR

You can use angular event mechanism and trigger an event so that gridcomponent can catch the event and get the new query . But service way of doing is better . Since you have the FilterComponent inside router, its difficult to use event mechanism here.

In either case you can use the pipe as mentioned in your question.

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

@Pipe({
  name: 'topicFilterPipe'
})
export class TopicFilterPipePipe implements PipeTransform {

  public transform(value: Array<any>, keys: string, term: string) {
    console.log('pipe has run');
    if (!term) return value;
    return (value || []).filter((item) => keys.split(',').some(key => item.hasOwnProperty(key) && new RegExp(term, 'gi').test(item[key])));

  }

}

You can either use the pipe you mentioned in the question or filter the list yourself in the component directly.

If you still have problems, create a plnkr, I can update it for you with this answer.