DevExtreme v26.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

Angular Filter Builder - Customization

In this demo, a custom filter operation, "anyof", is implemented. The calculateFilterExpression function converts it into DataSource-compatible operations. The compatible and incompatible filter expressions are displayed under the FilterBuilder.

The set of available group operations is limited to "and" and "or" using the groupOperations array, and the groups’ maximum nesting level is limited to one using the maxGroupLevel.

Backend API
<dx-filter-builder [fields]="fields" [(value)]="filter" [groupOperations]="groupOperations" [customOperations]="customOperations" (onInitialized)="updateTexts($event)" (onValueChanged)="updateTexts($event)" > <dx-tag-box *dxTemplate="let condition of 'tagBoxTemplate'" [value]="condition.value" [items]="categories" [inputAttr]="{ 'aria-label': 'Category' }" (onValueChanged)="condition.setValue($event.value)" width="auto" > </dx-tag-box> </dx-filter-builder> <div class="results"> <div> <b>Value</b> <pre>{{ filterText }}</pre> </div> <div> <b>DataSource's filter expression</b> <pre>{{ dataSourceText }}</pre> </div> </div>
import { ChangeDetectorRef, Component, enableProdMode, provideZoneChangeDetection, } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { DxListModule, DxButtonModule, DxTagBoxModule } from 'devextreme-angular'; import { DxFilterBuilderModule, DxFilterBuilderComponent, DxFilterBuilderTypes } from 'devextreme-angular/ui/filter-builder'; import { Service } from './app.service'; type FilterBuilderValue = ReturnType<DxFilterBuilderComponent['instance']['getFilterExpression']>; if (!/localhost/.test(document.location.host)) { enableProdMode(); } const anyOfOperation = { name: 'anyof', caption: 'Is any of', icon: 'check', editorTemplate: 'tagBoxTemplate', calculateFilterExpression: (filterValue: string[], field: Record<string, unknown>) => filterValue?.flatMap( (value) => [[field.dataField, '=', value], 'or'], ).slice(0, -1), } as const; const TAB_SIZE = 4; @Component({ selector: 'demo-app', providers: [Service], templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ DxListModule, DxButtonModule, DxTagBoxModule, DxFilterBuilderModule, ], }) export class AppComponent { filterText: string; dataSourceText: string; fields: string[]; filter: (string[] | string)[]; categories: string[]; customOperations: typeof anyOfOperation[] = [anyOfOperation]; groupOperations = ['and', 'or']; constructor(service: Service, private changeDetectorRef: ChangeDetectorRef) { this.fields = service.getFields(); this.filter = service.getFilter(); this.categories = service.getCategories(); } updateTexts(e: DxFilterBuilderTypes.InitializedEvent) { this.filterText = AppComponent.formatValue(e.component.option('value')); this.dataSourceText = AppComponent.formatValue(e.component.getFilterExpression()); this.changeDetectorRef.detectChanges(); } private static formatValue(value: FilterBuilderValue, spaces = TAB_SIZE) { if (value && Array.isArray(value[0])) { return `[${AppComponent.getLineBreak(spaces)}${ (value as string[]).map( (item: FilterBuilderValue) => (Array.isArray(item[0]) ? AppComponent.formatValue(item, spaces + TAB_SIZE) : JSON.stringify(item)), ).join(`,${AppComponent.getLineBreak(spaces)}`) }${AppComponent.getLineBreak(spaces - TAB_SIZE)}]`; } return JSON.stringify(value); } private static getLineBreak = (spaces: number) => `\r\n${new Array(spaces + 1).join(' ')}`; } bootstrapApplication(AppComponent, { providers: [ provideZoneChangeDetection({ eventCoalescing: true, runCoalescing: true }), ], });
::ng-deep .results { margin-top: 50px; display: flex; justify-content: space-between; } ::ng-deep .results > div { flex-basis: 49%; max-width: 50%; background-color: rgba(191, 191, 191, 0.15); position: relative; } ::ng-deep .results b { position: absolute; top: -25px; } ::ng-deep .results pre { padding: 10px 20px; height: 100%; font-size: 13px; overflow: auto; box-sizing: border-box; } ::ng-deep .dx-tagbox { min-width: 150px; } ::ng-deep .dx-filterbuilder { background-color: transparent; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.14), 0 0 2px 0 rgba(0, 0, 0, 0.12); border-radius: 6px; padding: 15px; margin: 24px; } ::ng-deep .dx-filterbuilder .dx-numberbox { width: 80px; }
import { Injectable } from '@angular/core'; export class Product { ID: number; Name: string; Price: number; Current_Inventory: number; Backorder: number; Manufacturing: number; Category: string; ImageSrc: string; } const filter: any[] = [ ['Category', 'anyof', ['Automation', 'Monitors']], 'or', [ ['Category', '=', 'Televisions'], 'and', ['Price', 'between', [2000, 4000]], ], ]; const categories: string[] = [ 'Video Players', 'Televisions', 'Monitors', 'Projectors', 'Automation', ]; const fields: any[] = [{ dataField: 'Name', }, { dataField: 'Price', dataType: 'number', format: 'currency', }, { dataField: 'Current_Inventory', dataType: 'number', caption: 'Inventory', }, { dataField: 'Category', filterOperations: ['=', 'anyof'], lookup: { dataSource: categories, }, }, ]; @Injectable() export class Service { getFields(): any[] { return fields; } getCategories(): string[] { return categories; } getFilter(): any[] { return filter; } }
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/26.1.5/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" /> </head> <body class="dx-viewport"> <div class="demo-container"> <demo-app>Loading...</demo-app> </div> <script src="https://cdn.jsdelivr.net/npm/zone.js@0.16.2/bundles/zone.umd.js"></script> <script src="./bundle.js" type="module"></script> </body> </html>