Our Syncfusion Angular Query Builder is a graphical user interface component used to build queries. It supports data binding, templates, and importing and exporting queries from and to JSON and SQL formats. Query Builder can be used to generate predicates that are used as conditions in DataManager. It can also auto-populate a data source and map it to appropriate fields from an array of JavaScript objects. In this blog, I am going to walk you through the templating feature, which is used to customize the UI of the Angular Query Builder.
To provide flexibility in customization and a better user experience, the Angular Query Builder offers templates support. A template can include a mixture of static HTML and web controls. The following UI customization options are available:
Users can customize the user interface to display the value column of a rule (condition) using the template property.
In the following sample code, the:
<ejs-querybuilder id="querybuilder" #querybuilder [rule] = "importRules"> <e-columns> <e-column field="Category" label="Category" type="string"></e-column> <e-column field="PaymentMode" label="Payment Mode" type="string" [operators]="customOperators" [template]="paymentTemplate"> </e-column> <e-column field="TransactionType" label="Transaction Type" type="string" [operators]="customOperators" [template]="transactionTemplate"> </e-column> <e-column field="Description" label="Description" type="string"></e-column> <e-column field="Date" label="Date" type="string"></e-column> <e-column field="Amount" label="Amount" type="string"></e-column> </e-columns> </ejs-querybuilder> <ng-template #paymentTemplate let-data> <ejs-dropdownlist [dataSource]='paymentDS' [value]='data.rule.value' (change)="paymentChange($event, data.ruleID)"> </ejs-dropdownlist> </ng-template> <ng-template #transactionTemplate let-data> <ejs-checkbox label='Is Expense' [checked]='data.rule.value === "Expense" ? true: false' (change)="transactionChange($event, data.ruleID)"> </ejs-checkbox> </ng-template>
import { Component, ViewChild, OnInit } from '@angular/core'; import { QueryBuilderComponent } from './../index'; import { ActionEventArgs, RuleModel } from '@syncfusion/ej2-querybuilder'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { @ViewChild('querybuilder') qryBldrObj: QueryBuilderComponent; public paymentDS: string[] = ['Cash', 'Debit Card', 'Credit Card', 'Net Banking']; public importRules: RuleModel; public customOperators: any; ngOnInit(): void { this.importRules = { 'condition': 'and', 'rules': [{ 'label': 'Transaction Type', 'field': 'TransactionType', 'type': 'string', 'operator': 'equal', 'value': 'Expense' }, { 'label': 'Payment Mode', 'field': 'PaymentMode', 'type': 'string', 'operator': 'equal', 'value': 'Cash' }] }; this.customOperators = [ {value: 'equal', key: 'Equal'}, {value: 'notequal', key: 'Not Equal'} ]; } transactionChange(e: any, ruleID: string): void { let elem: HTMLElement; elem = document.getElementById(ruleID).querySelector('.e-rule-value'); this.qryBldrObj.notifyChange( e.checked === true ? 'Expense' : 'Income', elem, 'value'); } paymentChange(e: any, ruleID: string): void { let elem: HTMLElement; elem = document.getElementById(ruleID).querySelector('.e-rule-value'); this.qryBldrObj.notifyChange(e.value as string, elem, 'value'); } }
The following .gif image shows the UI customizations done in the Angular Query Builder using the Value Template property.
Users can also customize the user interface to display the entire rule using the ruleTemplate property.
Customize the Age field using the ruleTemplate property as shown in the following sample code. Here, set the default condition as greater than or equal and render the slider component for the value column. Finally, show the result using a table in the rule UI template.
<ejs-querybuilder id="querybuilder" #querybuilder width="100%" [rule] = "importRules" (actionBegin)="actionBegin($event)"> <e-columns> <e-column field="EmployeeID" label="Employee ID" type="number"></e-column> <e-column field="FirstName" label="First Name" type="string"></e-column> <e-column field="LastName" label="LastName" type="string"></e-column> <e-column field="Age" label="Age" type="number" [ruleTemplate]="ruleTemplate"></e-column> <e-column field="City" label="City" type="string"></e-column> <e-column field="Country" label="Country" type="string"></e-column> </e-columns> </ejs-querybuilder>
<ng-template #ruleTemplate let-data> <div class="e-rule e-rule-template"> <div class="e-rule-header"> <div class="e-rule-filter"> <ejs-dropdownlist (change)="fieldChange($event)" [fields]="data.fields" [dataSource]="data.columns" [value]="data.rule.field"></ejs-dropdownlist> </div> <div *ngIf="data.rule.type ==='number'" class="e-rule-value e-slide-val"> <ejs-slider [value]='data.rule.value' [ticks]='rangeticks' min=30 max=50 id="{{data.ruleID}}_valuekey0" (change)="valueChange($event, data.ruleID)"></ejs-slider> </div> <div class="e-rule-btn"> <button id="{{data.ruleID}}_option" (click)="viewDetails(data.ruleID)" class="e-primary e-btn e-small"> <span class='e-content'>View Details</span> </button> <button class="e-removerule e-rule-delete e-btn e-small e-round"> <span class="e-btn-icon e-icons e-delete-icon"></span> </button> </div> </div> <div id="{{data.ruleID}}_section" class="e-rule-content e-hide"> <table id="{{data.ruleID}}_datatable" class='e-rule-table e-hide'> <thead> <tr><th>EmployeeID</th><th>FirstName</th><th>Age</th></tr> </thead> <tbody></tbody> </table> </div> </div> </ng-template>
ejs-querybuilder { display: block; } .e-rule-template { padding-bottom: 12px; } .e-query-builder .e-slider-container.e-horizontal { padding: 0px 0 0 18px; height: 0; } .e-query-builder .e-slider-container.e-horizontal .e-slider { top: calc(50% - 7px); } .e-query-builder .e-slide-val { width: 35%; } .e-rule-btn { float: right; padding-top: 12px; } .e-query-builder .e-hide { display: none; } .e-rule-content { margin: 0px 0 0 13px; height: 180px; width: 335px; overflow-y: auto; border: 1px solid #e0e0e0; } .e-rule-table { border-collapse: collapse; } .e-rule-table th, .e-rule-table td { border: solid #e0e0e0; border-width: 1px 0 0; padding: 8px 21px; }
export class AppComponent implements OnInit { @ViewChild('querybuilder') qryBldrObj: QueryBuilderComponent; public actionArgs: ActionEventArgs; public importRules: RuleModel; public rangeticks: Object; ngOnInit(): void { this.importRules = { 'condition': 'and', 'rules': [{ 'label': 'Age', 'field': 'Age', 'type': 'number', 'operator': 'greaterthanorequal', 'value': 32 }] }; this.rangeticks = { placement: 'Before', largeStep: 5, smallStep: 1, showSmallTicks: true }; } actionBegin(args: ActionEventArgs): void { if (args.requestType === 'template-initialize') { this.actionArgs = args; args.rule.operator = 'greaterthanorequal'; if (args.rule.value == '') { args.rule.value = 32; } } } fieldChange(e: any): void { this.qryBldrObj.notifyChange(e.value, e.element, 'field'); }; valueChange(e: any, ruleID: string): void { let elem: HTMLElement = document.getElementById(ruleID); this.qryBldrObj.notifyChange(e.value as Date, elem, 'value'); this.refreshTable(this.qryBldrObj.getRule(elem), ruleID); } viewDetails(ruleID: string): void { let ruleElem: HTMLElement = document.getElementById(ruleID); let element: HTMLElement = document.getElementById(ruleID + '_section'); if (element.className.indexOf('e-hide') > -1) { this.refreshTable(this.qryBldrObj.getRule(ruleElem), ruleID); element.className = element.className.replace('e-hide', ''); document.getElementById(ruleID + '_option').querySelector('.e-content').textContent = 'Hide Details'; } else { element.className += ' e-hide'; document.getElementById(ruleID + '_option').querySelector('.e-content').textContent = 'View Details'; } } refreshTable(rule: RuleModel, ruleID: string): void { let template: string = '<tr><td>${EmployeeID}</td><td>${LastName}</td><td>${Age}</td></tr>'; let compiledFunction: any = compile(template); let predicate: Predicate = this.qryBldrObj.getPredicate({condition: 'and', rules: [rule]}); let dataManagerQuery: Query = new Query().select(['EmployeeID', 'LastName', 'Age']).where(predicate); let result: object[] = new DataManager(employeeData).executeLocal(dataManagerQuery); let table: HTMLElement = document.getElementById(ruleID + '_datatable') as HTMLElement; if (table) { if (result.length) { table.style.display = 'block'; } else { table.style.display = 'none'; } table.querySelector('tbody').innerHTML = ''; result.forEach((data) => { table.querySelector('tbody').appendChild(compiledFunction(data)[0].querySelector('tr')); }); } } }
The following .gif image shows all the UI customizations done in the Query Builder using the Rule Template property.
You can find the projects in the following UG documentation links,
I hope you now have a better understanding of the UI customization in the Angular Query Builder control. What else do you expect from our Query Builder? Please share your thoughts in the comments section.
Our Query Builder component is also available for the Blazor, ASP.NET (Core, MVC), JavaScript, React, and Vue platforms.
If you’re already a Syncfusion user, you can download the product setup to try out this control. Otherwise, you can download a free 30-day trial.
If you have any questions about these features, please contact us through our support forum, Direct-Trac, or feedback portal. We are happy to assist you!
If you like this blog post, we think you’ll also like the following articles, too: