Ajay Verma
Ajay Verma

Reputation: 25

Multiple search filters in angular

I have created 3 search fields: 1. Name.... 2. Subject.... 3.Price....
All of them are working good if considered separately. eg. from a list of elements if I search by name, I get all the results from that name or if I search by Subject, I get all the results of that subject and the same for price also.

But the problem is I don't know how to make all the filters work simultaneously for me i.e. If I want a person named "BOB" who can teach "English" at price "500-600". How to achieve this link between multiple filters This is the image of my filter

component.ts
filterSearch an array that contains all the data of the users and tempFilterSearch is used to show it at html

searchSubject($event) {
    this.subject = $event.target.value;
    if(this.price == null){
        this.tempFilterSearch = this.filterSearch.filter((res) => {         
            return (                    
                res.subject1.toLocaleLowerCase().match(this.subject.toLocaleLowerCase()) ||
                res.subject2.toLocaleLowerCase().match(this.subject.toLocaleLowerCase()) ||
                res.subject3.toLocaleLowerCase().match(this.subject.toLocaleLowerCase())
                );
            });
    }else if(this.price != null){
        this.tempFilterSearch = this.filterSearch.filter((res)=>{
            console.log(res);
            if((Number(res.price1))>=(Number(this.lowerValue))
                &&(Number(res.price1))<=(Number(this.higherValue)) 
                &&(res.subject1.match(this.subject)) || (res.subject2.match(this.subject))|| (res.subject3.match(this.subject))){
                    return res.subject1 || res.subject2 || res.subject3;
            }
        })
    }

searchName() {  
    this.tempFilterSearch = this.filterSearch.filter((response)=>{
        return response.fullName.toLocaleLowerCase().match(this.name.toLocaleLowerCase());
    })
    
}

searchPrice($event) {
    this.price = $event.target.value.split("-");
    this.lowerValue = this.price[0];
    this.higherValue = this.price[1];
    if (this.subject == null) {
        this.tempFilterSearch = this.filterSearch.filter((res) => {
            if((Number(res.price1))>=(Number(this.lowerValue))&&(Number(res.price1))<=(Number(this.higherValue)))
                return res.price1.toLocaleLowerCase();
            });
    }else if(this.subject != null){
        this.tempFilterSearch = this.filterSearch.filter((res)=>{
            if((Number(res.price1))>=(Number(this.lowerValue))
                &&(Number(res.price1))<=(Number(this.higherValue)) ){
                    return res.price1.toLocaleLowerCase();
            }
        })
    }
}

component.html

 <div class="tutorWrapper" *ngFor="let element of tempFilterSearch">

What I want is a solution so that all the filters can work simultaneously.

Upvotes: 1

Views: 2571

Answers (2)

Mohamed Aljamil
Mohamed Aljamil

Reputation: 399

I am answering this from the phone so if this answer isnt good enough ill edit it later from pc.

The way i would do it is to create all 3 possible filters as functions. In your case name, subject, price, And create a variable to hold an array of the filtered objects.

On 1st filter (name) my function would take the main list array and returns all matching elements into the filtered variable.

Then subject filter takes the filtered variable and filters it further for subject. And re assign output back into filteredarray. Same goes for price.

Then you ngfor the filtered array.

!!EDIT!!

as per your comment on how to solve the already filtered array issue. ill write some pseudo code below:

mainArray = [...arrayItems];

filteredArray: Items[];


onFilterButtonPressed(name, subject, price) {

 // here you can use if to see if some value hasnt been provided and skip that filter
  nameFilter();
  subjectFilter();
  priceFilter();
}


nameFilter(name){
  filteredArray = mainArray.filter(item => item.name === name)
}

subjectFilter(subject){
  filteredArray = filteredArray.filter(item => item.subject === subject)
}

priceFilter(price){
  filteredArray = filteredArray.filter(item => item.price === price)
}
<button (click)="onFilterButtonPressed(pass your filter values here)">click to filter</button

The above code is ugly and requires name variable to be there however to go around that we use a piping function that uses reduce() to apply our filters

//here we create a function that uses reduce which takes the current array and applies the next filter function to it and changes that current array and so on
function pipe(...fns) {
  return (arg) => fns.reduce((prev, fn) => fn(prev), arg);
}
// here we call the function we made and pass the filter functions we created to it
//our filter functions must filter and return the filtered array, they also check whether the filter exists or not, if it doesnt then return the array unchanged
filteredArray =  pipe(nameFilter, subjectFilter, priceFilter)(MainArray)

Hope this helps

Upvotes: 1

Hanna Haritha Valli
Hanna Haritha Valli

Reputation: 116

In your sample code all the seperate filters are applied to fresh this.filterSearch. That means, every time any filtering starts, it resets the previous filtered results and starts from beginning.

You need to merge the filters in to one logic to apply all on the same array and have only one output array. Any time a change occurs on filters, it will start from the original array and apply all filters one by one.

For example;

searchSubject($event) {
    this.subject = $event.target.value;
    this.applyFilters();
}

searchName() {  
    //this.name is already assigned 
    this.applyFilters();    
}

searchPrice($event) {
    this.price = $event.target.value.split("-");
    this.lowerValue = this.price[0];
    this.higherValue = this.price[1];
    this.applyFilters();
}



applyFilters() {
    let temp = this.filterSearch;


    // SUBJECT
    if(this.price == null){
        temp = temp.filter((res) => {           
            return (                    
                res.subject1.toLocaleLowerCase().match(this.subject.toLocaleLowerCase()) ||
                res.subject2.toLocaleLowerCase().match(this.subject.toLocaleLowerCase()) ||
                res.subject3.toLocaleLowerCase().match(this.subject.toLocaleLowerCase())
                );
            });
    }else if(this.price != null){
        temp = temp.filter((res)=>{
            if((Number(res.price1))>=(Number(this.lowerValue))
                &&(Number(res.price1))<=(Number(this.higherValue)) 
                &&(res.subject1.match(this.subject)) || (res.subject2.match(this.subject))|| (res.subject3.match(this.subject))){
                    return res.subject1 || res.subject2 || res.subject3;
            }
        })
    }


    // NAME
    temp = temp.filter((response)=>{
        return response.fullName.toLocaleLowerCase().match(this.name.toLocaleLowerCase());
    })
    

    // PRICE
    if (this.subject == null) {
        temp = temp.filter((res) => {
            if((Number(res.price1))>=(Number(this.lowerValue))&&(Number(res.price1))<=(Number(this.higherValue)))
                return res.price1.toLocaleLowerCase();
            });
    }else if(this.subject != null){
        temp = temp.filter((res)=>{
            if((Number(res.price1))>=(Number(this.lowerValue))
                &&(Number(res.price1))<=(Number(this.higherValue)) ){
                    return res.price1.toLocaleLowerCase();
            }
        })
    }

    this.tempFilterSearch = temp;
}

Your seperate filter functions are not touched, logic is not tested. I only copy pasted to re-arrange your code in to merged function.

Upvotes: 2

Related Questions