Telerik blogs

While the Kendo UI for Angular Grid comes with built-in filtering functionality, sometimes you need to allow users to control what the filters show. We can do that too. Let’s take a look.

In an earlier post, I walked through the mechanics of filtering with the Angular Data Grid from Progress Kendo UI. In that post, however, I focused on the Grid’s support for letting the user filter the rows in the grid and I only outlined the mechanics of integrating filtering with the rest of the application’s UI.

In this post, I’m going to look at a more realistic example how you can integrate filtering into an application so that, as the user interacts with the application’s UI, your code controls what rows the Grid displays.

My case study app is relatively simple: I have a Grid displaying a list of customers and dropdown list of countries. As the user selects a country from the dropdown list, my code will update the Grid to show only the customers in that country. To make matters more interesting, I’ll also throw in a checkbox that lets the user select customers based on whether the customer is in the company’s loyalty program.
Here’s what the UI looks like:

A web page with a dropdown list at the top of the page showing “Pick a country”. Below it is a checkbox with a label saying “Loyalty Program Only.” Below that is a Grid showing three customers. The rows show that two customers are in the USA, one customer is in Canada; One customer (from the USA) is in the loyalty program and the other two customers (one from the USA, one from Canada) are not.

Defining the UI

The Customer class that this grid is showing has six properties. However, for this case study, I’m only interested in the country and inLoyaltyProgram properties:

export class Customer 
{
  constructor(
    public id: number,
    public country: string,
    public inLoyaltyProgram: Boolean,
    public custName: string,
    public signUpDate: Date | null,
    public amountPurchased: number
  ) { }
}

The markup to define the dropdown list and checkbox is going to look like this:

Select Country: 
      <kendo-dropdownlist 
           [data]="countries"
           defaultItem=”Pick a Country”
           (selectionChange)="CountryChange($event)"> 
      </kendo-dropdownlist><br/><br/>
  Loyalty Program Only: &nbsp; 
      <input type="checkbox" #loyalty kendoCheckBox 
           (change)="LoyaltyChange($event)"/>

Each of these components calls a function and passes the related event object when the user changes the component’s value. I’ll call these two functions (CountryChange for the dropdown list and LoyaltyChange for the checkbox) my “filter functions.”

I also need two customer arrays. The first array is a “complete/unfiltered” array of customers (probably fetched from some Web service). I bind my Grid, however, to a second array, called selectCustomers, which displays the currently selected customers. Initially, I want both arrays to be the same, so I initialize the arrays like this:

public customers: Customer[];  
public selectCustomers: Customer[] = [];  
  
  this.customers = [new Customer(1, "Peter Vogel", "USA", new Date(), 3250, true),
     …more Customers…
   ];
  this.selectCustomers = this.customers;

Finally, I define my Grid with the kendo-Grid component and bind the Grid to the selectCustomers list. I also define each column in the Grid so that I can bind each column to one of my company’s properties, control how the data in the column is formatted, set the heading at the top of the column, and specify the column’s width.

All that Grid markup looks like this:

    <kendo-Grid
      [kendoGridBinding]="selectCustomers"
      [height]="250"
      (filterChange)= "this.filterCustomers($event)">
        <kendo-Grid-column title="Id" field="id" [width]="50"></kendo-Grid-column> 
        <kendo-Grid-column title="Name" field="custName" [width]="150"></kendo-Grid-column>
        <kendo-Grid-column title="Purchase" field="amountPurchased" [width]="125" format="99,999"></kendo-Grid-column>
        <kendo-Grid-column title="Signed Up" field="signUpDate" [width]="200" format="MMM-dd-yyyy"></kendo-Grid-column>
        <kendo-Grid-column title="In Program" field="inLoyaltyProgram" [width]="100"></kendo-Grid-column>
    </kendo-Grid>

Filtering Infrastructure

To filter a Grid, I need a CompositeFilterDescriptor that holds zero or more FilterDescriptors (if the CompositeFilterDescriptor has no FilterDescriptors, then no rows are “filtered out”). I’ll need to import those two classes and, since I’m also going to need the filterBy function later on, I’ll import it too:

import { CompositeFilterDescriptor, FilterDescriptor, filterBy }
   from "@progress/kendo-data-query";

A FilterDescriptor has three properties that matter for this case study:

  • field: Name of the property to filter on
  • operator: The comparison operator to use (e.g., eq, neq, gt, etc.)
  • value: the value to compare

It makes sense to me to set up two FilterDescriptors—one for filtering by customer and one for filtering by loyalty program—as properties. This lets me set all the properties on each FilterDescriptor to some default values. This simplifies my later code: As the user makes changes in the dropdown list and checkbox, the only property I have to change in my two filter functions is each FilterDescriptor’s value property.

Declaring those two FilterDescriptor properties looks like this:

countryFilter: FilterDescriptor = {field:"country", operator:"eq", value:"Pick a country"};
loyaltyFilter: FilterDescriptor = {field:"inLoyaltyProgram", operator:"eq", value:false};

Now, in my filter functions, I just retrieve the current value from the event passed to the function and set the value property in the appropriate FilterDescriptor. That’s pretty straightforward for the dropdown list because the dropdown list’s selection event just passes the value currently selected in the dropdown:

CountryChange(e:string): void
  {
    this.countryFilter.value = e;    
    this.CreateFilter();
  }

It’s a little more complicated for the checkbox because that function is passed an Event object. However, I can cast that Event object’s target property as an HtmlInput object and then retrieve the element’s checked property to set the FieldDescriptor’s value property:

LoyaltyChange(e:Event): void
  {
    const cbx = e.target as HTMLInputElement;
    this.loyaltyFilter.value = cbx.checked; 
    this.CreateFilter();
  }

The CreateFilter function I’m calling at the end of each of these functions assembles the two FilterDescriptors into a CompositeFilter and actually filters my data (you’ll see that function after the following short interruption).

A UX Interruption

I’ve only given the user the ability to filter on whether the customer is in the loyalty program—the user can’t filter for customers not in the program. That’s because I used a checkbox in my user interface to control this filtering option.

While the Kendo UI Dropdown list has an “indeterminate” state that allows the checkbox to indicate that it hasn’t been touched yet, once the user checks the checkbox, the checkbox only alternates between true and false states. If I used false to filter the list to those customers not in the loyalty program then, once a user checks the checkbox, the user would be restricted to switching between using true to see those customers in the program and false to see those customers not in the program … but never all the customers.

If I wanted to let the user have the choice of three states for the loyalty program filter (in the loyalty program, not in the loyalty program, and “don’t care about the loyalty program”), I would have had to use some other combination of components (perhaps two radio buttons or another dropdown list).

I avoided this problem with the countries dropdown list by setting the dropdown list’s defaultItem property to “Pick a Country.” If the user doesn’t change that default entry or returns to that default entry after filtering on some country, I’ll go back to showing all the customers, regardless of country.

Back to the code.

Filtering the Customers

Now that I’ve built my FilterDescriptors, I combine them in my CreateFilter method.

In that CreateFilter function, I check for the four possible combinations of values in the two FilterConditions and use them to load the CompositeFilterDescriptor’s Filters array:

  • If the LoyaltyFilter is set to false and the CustomerFilter is set to the dropdown list’s default value, I don’t set any filters.
  • If the LoyaltyFilter is set to true and CustomerFilter to some country (i.e., not the default value), I load both filters.
  • For any other combination, I load only the filter that has its value set.

When you do have multiple items in the Filters condition, you must set the CompositeFilterDescriptor’s logic property to “and” or “or” to indicate how the filters are to be combined. When I do use both filters, I want them to be “and”ed together so, in my CompositeFilterDescriptor, I’ve set the logic property to “and.”

Where these is only one (or no) filter, it doesn’t matter what the logic property is set to—having the property set to “and” is just fine. That means (lucky me!) that I never have to change the logic property: I either want the filters “and”ed together or I don’t care.

Here’s the code that builds my ComponsiteFilterDescriptor (there are probably more sophisticated ways of doing this, but this version has the advantage of being obvious):

GridFilter:CompositeFilterDescriptor = {filters:[],logic:"and"};
  CreateFilter()
  {
    if (!this.loyaltyFilter.value && this.countryFilter.value == "Pick a country")
    {
      this.GridFilter.filters = [];
    }
    if (!this.loyaltyFilter.value && this.countryFilter.value != "Pick a country")
    {
      this.GridFilter.filters = [this.countryFilter];
    }
    if (this.loyaltyFilter.value && this.countryFilter.value == "Pick a country")
    {
      this.GridFilter.filters = [this.loyaltyFilter];
    }
    if (this.loyaltyFilter.value && this.countryFilter.value != "Pick a country")
    {
      this.GridFilter.filters = [this.loyaltyFilter, this.countryFilter];
    }  

At the end of my CreateFilter function, I can finally filter my complete array of customers into the selectCustomers array that my Grid is bound to. To do that, I use the filterBy function I imported earlier, passing the complete customers array and my CompositeFilterDescriptor. The code that uses the result of calling filterBy to set the array my Grid is bound to looks like this:

{
      this.GridFilter.filters = [this.loyaltyFilter, this.countryFilter];
    }
    this.selectCustomers = filterBy(this.customers, this.GridFilter);   
  }

Now, as the user interacts with the other parts of my application’s UI, my code is in control of what rows the Grid displays. And, I have to admit, while the Grid’s built-in filtering functionality is great, I do like to be in control.

Next up: You can combine the Kendo UI for Angular Grid’s filtering with your own custom code in any combination that you want … and then call a web service to retrieve the data you want as you need it. Read more: Server-Side Filtering with the Kendo Angular for UI Grid.

Peter Vogel
About the Author

Peter Vogel

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.

Related Posts

Comments

Comments are disabled in preview mode.