Handling combinations of optional parameters for an Angular filter pipe This is a simple implementation of an ng2 filter pipe, which can currently take 2 optional facets: &lt;string&gt;status and &lt;string&gt;description. A "filter pipe" is an angular2 view decorator.

The filter receives a list of objects (values) which I want to filter by some criteria specific to the type of data object passed to it. The types of criteria are known in advance for each type of object.

Currently, I have implemented only one object type, a "maintenance event", which can be filtered by:


a status field, which is a fixed enum of strings, and
a free-text description


The filter is implemented in the view like so:

&lt;div *ngFor="let event of maintenanceEvents | filter:'MAINTENANCE_EVENT':statusFilter:wordFilter"&gt;


where statusFilter and wordFilter are populated by a form.

The rest parameter (...filters) was used to allow any future arbitrary number of filters to be passed to the common filter module. So, in future, I could implement a filter like this:

&lt;div *ngFor="let person of people | filter:'PEOPLE':title:permissions:locations:reports"&gt;


This is my complete pipe module:

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

@Pipe({ name: 'filter' })
export class FilterPipe implements PipeTransform {

    transform(values: any, type: string, ...filters: Object[]): any {

        switch (type) {
            case 'MAINTENANCE_EVENT':

                const ALL = "Show all";
                const BLANK = "";
                const DESCRIPTION_FILTER = new RegExp(&lt;string&gt;filters[1], 'gi');

                if (filters.length !== 2) {
                    throw new SyntaxError('MAINTENANCE_EVENT requires exactly 2 filter parameters: &lt;string&gt;status and &lt;string&gt;description')
                }

                if (filters[0] === ALL &amp;&amp; filters[1] === BLANK) {
                    return values;
                }
                else {
                    return values.filter(value =&gt; {
                        if (value.hasOwnProperty('status') &amp;&amp; value.hasOwnProperty('part') &amp;&amp; value.part.hasOwnProperty('description')) {
                            if (filters[0] === ALL &amp;&amp; filters[1] !== BLANK) {
                                return DESCRIPTION_FILTER.test(value['part']['description']);
                            }
                            else if (filters[0] !== ALL &amp;&amp; filters[1] === BLANK) {
                                return value.status === filters[0];
                            }
                            else if (filters[0] !== ALL &amp;&amp; filters[1] !== BLANK) {
                                return value.status === filters[0] &amp;&amp; DESCRIPTION_FILTER.test(value['part']['description']);
                            }
                        }
                        else {
                            console.error("Attempting to filter by properties not present in available &lt;MaintenanceEvent&gt;s");
                        }
                    });
                }

            default:
                // Return everything
                return values;
        }
    }
}


Particularly, I'm interested in an alternative to the if, else if, else if ... else if N chain, since this will become wholly unmaintainable for filters with more complex facets (we plan for as many as 9 in future).
Direction

My approach would be based on the Strategy design pattern. Each of the original if-elseif-elseif-... sections is now becoming an IRule object that represents the rule. This object does two things:

A) It defines whether it may or may not be applied to the given filter input;

B) It defines the application function (logic) itself.

In order to select a proper rule dynamically in run time, we need a registry of the rules which is implemented as a simple array.

Benefits

Implementation of a new "rule" becomes almost trivial: we'll need to create a rule object itself, and add it to a proper place in the list of registry entries, so that it's picked up.

Code

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

const ALL = "Show all";
const BLANK = "";

/**
 * Represents a contract of a single rule.
 */
interface IRule {
    /**
     * A predicate that sayw whether the rule may or may not be applied to a value based on the filter.
     */
    isApplicableTo(filters: Object[]): boolean;

    /**
     * Implementation of rule applied to a value based on filter parameters.
     */
    applyTo(filters: Object[], value: any): boolean;
}

/**
 * The complete list of rules that your system supports.
 * NOTICE that the order of the rule is important.
 * If more than one rule is applicable to a filters object,
 * the very first applicable rule is applied.
 */
const RULES_REGISTRY: IRule[] = [
    new AllMatchingRegexRule(),
    new MatchingStatusRule(),
    new SomeMatchingRegexRule()
];

/**
 * Three sample rules based on your original code.
 */

class AllMatchingRegexRule implements IRule {
    isApplicableTo(filters: Object[]): boolean {
        const safeFilters = getSafeFilters(filters);
        return isAllFilter(safeFilters) &amp;&amp; isNotBlankFilter(safeFilters);
    }

    applyTo(filters: Object[], value: any): boolean {
        return descriptionRegexMatched(filters[1], value);
    }
}

class MatchingStatusRule implements IRule {
    isApplicableTo(filters: Object[]): boolean {
        const safeFilters = getSafeFilters(filters);
        return isNotAllFilter(safeFilters) &amp;&amp; isBlankFilter(safeFilters);
    }

    applyTo(filters: Object[], value: any): boolean {
        return statusMatches(filters, value);
    }
}

class SomeMatchingRegexRule implements IRule {
    isApplicableTo(filters: Object[]): boolean {
        const safeFilters = getSafeFilters(filters);
        return isNotAllFilter(safeFilters) &amp;&amp; isNotBlankFilter(safeFilters);
    }

    applyTo(filters: Object[], value: any): boolean {
        return value.status === filters[0] &amp;&amp; descriptionRegexMatched(filters[1], value);;
    }
}

// Reusable working horse lambdas (use in Rules)

const getSafeFilters = (filters: Object[]) =&gt; filters &amp;&amp; filters.length &gt;= 2 ? filters : new Object[2];

const descriptionRegexMatched = (pattern: any, value: any) =&gt; new RegExp(pattern, 'gi').test(value['part']['description']);

const statusMatches = (filters: Object[], value: any) =&gt; value.status === filters[0];

const isAllFilter = (filters: Object[]) =&gt; filters[0] === ALL;
const isNotAllFilter = (filters: Object[]) =&gt; !isAllFilter(filters);

const isBlankFilter = (filters: Object[]) =&gt; filters[1] === BLANK;
const isNotBlankFilter = (filters: Object[]) =&gt; !isBlankFilter(filters);


Consuming code (that uses the Rule registry)

Now your filter can be implemented like this:

@Pipe({ name: 'filter' })
export class FilterPipe implements PipeTransform {

    transform(values: any, type: string, ...filters: Object[]): any {
        switch (type) {
            case 'MAINTENANCE_EVENT':
                if (filters.length !== 2) {
                    throw new SyntaxError('MAINTENANCE_EVENT requires exactly 2 filter parameters: &lt;string&gt;status and &lt;string&gt;description')
                }

                if (isAllFilter(filters) &amp;&amp; isNotBlankFilter(filters)) {
                    return values;
                } else {
                    return values.filter(value =&gt; {
                        const ruleToApply = RULES_REGISTRY.find(rule =&gt; rule.isApplicableTo(filters));

                        if (!ruleToApply) {
                            throw new Error("Attempting to filter by properties not present in available &lt;MaintenanceEvent&gt;s");
                        }

                        return ruleToApply.applyTo(filters, value);
                    });
                }

            default:
                return values;
        }
    }
}


Disclaimer


I am very new to TypeScript so I may be using wrong idioms. The key in my answer is the design change, rather than implementation details.


P.S.

Notice that the same approach may be applied to type == 'MAINTENANCE_EVENT' 'OTHER_EVENT' .... And, speaking more generally, Strategy design pattern is almost always a working alternative to an if or switch. Of course, to get the benefits of the pattern, we need to write some boilerplate code (IRule + Rule registry), however it's a great trade of for the scenarios where there are a lot of rules, or the rules are frequently added/modified, or the rule selection strategy may change in run time.