How to Customize the UI of the Angular Query Builder | Syncfusion Blogs
Detailed Blog page Skeleton loader

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>

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');
  }
}

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>

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>

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;
}

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'));
      });
    }
  }
}

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 forumDirect-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:

Be the first to get updates

Satheeskumar S

Meet the Author

Satheeskumar S

Satheeskumar works as a product manager at Syncfusion, where he specializes in the development of web components with cutting-edge technologies. He is interested in the latest web technologies and provides solutions for great products.

Comments (2)

Would be great if we could have a Blazor version of this blog.

@ michael aston  

Hi Michael,

We have already showcased these template samples in our Blazor user guide. Please refer to the below link,

https://blazor.syncfusion.com/documentation/query-builder/templates/

Regards,
Satheeskumar S

Comments are closed.