DEV Community

Cover image for How to Customize the UI of the Angular Query Builder
Suresh Mohan for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

How to Customize the UI of the Angular Query Builder

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.

Template

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:

Value template

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:

  • Dropdown List component is used to render the custom transaction type field values.
  • Checkbox component is used to render the custom payment mode field values.

app.component.html code

<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>
Enter fullscreen mode Exit fullscreen mode

app.component.ts code

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');
  }
}
Enter fullscreen mode Exit fullscreen mode

The following .gif image shows the UI customizations done in the Angular Query Builder using the Value Template property.

UI customizations done in the Angular Query Builder using the Value Template property

Rule template

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.

app.component.html code

<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>
Enter fullscreen mode Exit fullscreen mode

Template code

<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>
Enter fullscreen mode Exit fullscreen mode

Customized CSS code

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;
}
Enter fullscreen mode Exit fullscreen mode

app.component.ts code

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'));
      });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The following .gif image shows all the UI customizations done in the Query Builder using the Rule Template property.

UI customizations done in the Angular Query Builder using the Rule Template property

Resources

You can find the projects in the following UG documentation links,

Conclusion

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:

Top comments (0)