Search code examples
javascriptangularionic-frameworkangular-filtersangular2-pipe

Custom Pipe product filter?


So what I'm trying to achieve is being able to toggle multiple filters on an NgFor with it updating each time a filter is toggled on or off.

I have this pipe:

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

@Pipe({
    name: 'filterProducts'
})
export class FilterProducts implements PipeTransform {
    transform(products: any[], args: {[field : string] : string[]}): any[] {  
        if (!products) return [];
        if (!args) return products;

        return products.filter((pr: any) => {

            for(let field in args) {

                if(args[field ].indexOf(pr[field]) === -1) {
                    return false;
                }
            }

            return true;
        });
    }
}

In my app.module.ts I have (Reduced for brevity):

import { FilterProducts } from '../pages/products/productsFilter.pipe';

@NgModule({
    declarations: [
        FilterProducts
    ]
})

In my product component I have:

public filterArgs : {[field : string] : string[]} = {};

constructor() { }

addBrandFilter(brands: string[]) {
    this.addFilter('brand', brands);
}

addFilter(field: string, values: string[]): void {
    this.filterArgs[field] = values;
}

And then on my NgFor I have (Reduced for brevity):

*ngFor="let product of products | filterProducts: filterArgs;"

<li class="search-filters__item" (click)="addBrandFilter(['Brand Name'])">Brand Name</li>

The way I have the template doing the filtering setup it has subsections of Price, Brand and Type. Within these are some buttons that the user can click on such as price being 0-50 or 50-100 etc. Ideally I'd like these to be able to be applied at the same time. So 0-50 and 50-100 would be selected and the ngFor filtered appropriately.

Is anyone able to give me some advice on how to achieve this? I've searched everywhere and can only find pipes based on search boxes or dropdowns.

EDIT

I've now updated this to reflect the help given to me by @PierreDuc. I'm now struggling to pass the relevant filters into filterArgs.

EDIT 2

I have now updated this again to reflect the progress thanks to @PierreDuc. Currently the ngFor is not filtering to the brand name passed through a (click).

Thanks in advance.


Solution

  • You state that your filterArgs is a string array. In that case you should change your filter to this:

    @Pipe({
        name: 'filterProducts'
    })
    export class FilterProducts implements PipeTransform {
        transform(products: any[], field : string, value : string[]): any[] {  
            if (!products) return [];   
            if (!value) return products;     
            return products.filter((pr: any) => {
                return value.indexOf(pr[field]) > -1
            });
        }
    }
    

    You can call this like so:

    *ngFor="let product of products | filterProducts: 'name' : ['cup', 'tea']"
    

    This will return all products where their name is cup or tea. Beware of case sensitivity if that applies to you

    To filter on multiple properties, you better send a {field : values} object to the pipe:

    To use your comment:

    let filterArgs : {[field : string] : string[]} = {
       name : ['brand', 'otherbrand'],
       type : ['A', 'B']
    };
    

    Then use this in your pipe:

    *ngFor="let product of products | filterProducts: filterArgs"
    

    And your pipe changes to this:

    @Pipe({
        name: 'filterProducts'
    })
    export class FilterProducts implements PipeTransform {
        transform(products: any[], args: {[field : string] : string[]}): any[] {  
            if (!products) return [];   
            if (!args) return products;     
            return products.filter((pr: any) => {
                for(let field in args) {
                    if(args[field ].indexOf(pr[field]) === -1) {
                        return false;
                    }
                }
                return true;
            });
        }
    }
    

    For the price evaluation I suggest you build a different pipe, where you can give a max and min settings and compare that to the price of the product

    update

    As an update to your code, you should assign the class property, not a local variable in your constructor. And the filterArds is an object, not an array. So no need to initialize it like you did:

    public filterArgs : {[field : string] : string[]} = {};
    
    constructor() { }
    
    addBrandFilter(brands: string[]) {
        this.addFilter('brand', brands);
    }
    
    addFilter(field: string, values: string[]): void {
        this.filterArgs[field] = values;
    }