npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ngx-ntk-query-builder

v20.26.2

Published

Angular query builder

Downloads

50

Readme

NGX NTK Query Builder

ngx-ntk-query-builder - Advanced Angular query builder component for creating complex database queries with visual interface and multi-language support

📋 Overview

The NGX NTK Query Builder is a powerful Angular component for building complex database queries through an intuitive visual interface. It supports multiple data types, operators, nested conditions, and provides a flexible architecture for creating dynamic queries. Perfect for applications requiring advanced search and filtering capabilities.

🚀 Installation

npm install ngx-ntk-query-builder

Dependencies

This library requires the following peer dependencies:

npm install @ngx-translate/core @ngx-translate/http-loader

📦 Features

Core Features

  • Visual Query Builder - Intuitive drag-and-drop interface
  • Multiple Data Types - String, Integer, Double, Date, Time, Boolean, Select
  • Advanced Operators - Equals, Contains, Greater/Less than, Null checks, etc.
  • Nested Conditions - Support for complex AND/OR logic
  • Multi-language Support - Internationalization ready
  • Custom Field Types - Extensible field type system
  • Real-time Validation - Built-in query validation

Advanced Features

  • Custom Operators - Extensible operator system
  • Field Settings - Configurable field behavior
  • Query Export - Export queries in various formats
  • Responsive Design - Mobile-friendly interface
  • Accessibility - ARIA support and keyboard navigation
  • Template Customization - Custom templates for fields

🔧 Usage

Basic Setup

import { NgModule } from "@angular/core";
import { NgxNtkQueryBuilderModule } from "ngx-ntk-query-builder";

@NgModule({
  imports: [NgxNtkQueryBuilderModule],
})
export class AppModule {}

Basic Implementation

<ngx-ntk-query-builder [fieldMap]="fieldMap" [(data)]="queryData" (queryChange)="onQueryChange($event)"> </ngx-ntk-query-builder>

Advanced Configuration

import { Component } from "@angular/core";
import { QueryBuilderFieldMap, QueryRuleSet } from "ngx-ntk-query-builder";

@Component({
  selector: "app-query-builder",
  template: ` <ngx-ntk-query-builder [fieldMap]="fieldMap" [settings]="querySettings" [(data)]="queryData" [disabled]="false" (queryChange)="onQueryChange($event)" (ruleAdded)="onRuleAdded($event)" (ruleRemoved)="onRuleRemoved($event)"> </ngx-ntk-query-builder> `,
})
export class QueryBuilderComponent {
  fieldMap: QueryBuilderFieldMap = {
    name: {
      name: "Name",
      type: "string",
      settings: {
        nullableOperators: true,
        emptyOperators: true,
      },
    },
    age: {
      name: "Age",
      type: "integer",
    },
    email: {
      name: "Email",
      type: "string",
    },
    status: {
      name: "Status",
      type: "select",
      options: [
        { name: "Active", value: "active" },
        { name: "Inactive", value: "inactive" },
        { name: "Pending", value: "pending" },
      ],
    },
    birthDate: {
      name: "Birth Date",
      type: "date",
    },
    isActive: {
      name: "Is Active",
      type: "boolean",
    },
  };

  querySettings = {
    allowRuleSets: true,
  };

  queryData: QueryRuleSet = {
    condition: "and",
    rules: [],
  };

  onQueryChange(query: QueryRuleSet): void {
    console.log("Query changed:", query);
    // Handle query changes
  }

  onRuleAdded(rule: any): void {
    console.log("Rule added:", rule);
  }

  onRuleRemoved(rule: any): void {
    console.log("Rule removed:", rule);
  }
}

📚 API Reference

Input Properties

| Property | Type | Default | Description | | ---------- | -------------------- | ------- | ---------------------------- | | fieldMap | QueryBuilderFieldMap | - | Field configuration map | | settings | QueryBuilderSettings | {} | Query builder settings | | data | QueryRuleSet | - | Query data (two-way binding) | | disabled | boolean | false | Disable query builder | | language | string | 'en' | Interface language |

Output Events

| Event | Type | Description | | ------------- | ------------ | ------------------ | | queryChange | EventEmitter | Query change event | | ruleAdded | EventEmitter | Rule added event | | ruleRemoved | EventEmitter | Rule removed event |

Interfaces

QueryBuilderFieldMap

export interface QueryBuilderFieldMap {
  [key: string]: QueryField;
}

QueryField

export interface QueryField {
  name: string; // Display name
  type: string; // Field type
  value?: string; // Default value
  options?: SelectOption[]; // Options for select type
  settings?: QueryFieldSettings; // Field settings
}

QueryFieldSettings

export interface QueryFieldSettings {
  nullableOperators?: boolean; // Allow null operators
  emptyOperators?: boolean; // Allow empty operators
}

QueryRule

export interface QueryRule {
  field: string; // Field name
  type: string; // Field type
  operator: string; // Operator
  value?: any; // Field value
}

QueryRuleSet

export interface QueryRuleSet {
  condition: string; // 'and' or 'or'
  rules: Array<QueryRuleSet | QueryRule>; // Nested rules
}

SelectOption

export interface SelectOption {
  name: string; // Display name
  value: any; // Option value
}

🎨 Customization

Custom Styling

// Custom query builder styles
.ngx-ntk-query-builder {
  .query-group-controls {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px;
    background-color: #f5f5f5;
    border-radius: 8px;
    margin-bottom: 16px;

    .condition-switcher {
      display: flex;
      gap: 16px;

      .switch-radio {
        display: none;
      }

      .switch-label {
        padding: 8px 16px;
        border: 1px solid #ddd;
        border-radius: 4px;
        cursor: pointer;
        transition: all 0.3s ease;

        &:hover {
          background-color: #e0e0e0;
        }

        &.active {
          background-color: #2196f3;
          color: white;
          border-color: #2196f3;
        }
      }
    }

    .actions {
      display: flex;
      gap: 8px;

      .query-btn {
        padding: 8px 16px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 14px;
        transition: all 0.3s ease;

        &.add {
          background-color: #4caf50;
          color: white;

          &:hover {
            background-color: #45a049;
          }
        }

        &.remove {
          background-color: #f44336;
          color: white;

          &:hover {
            background-color: #da190b;
          }
        }
      }
    }
  }

  .query-conditions {
    .rule-wrap {
      border: 1px solid #ddd;
      border-radius: 8px;
      padding: 16px;
      margin-bottom: 12px;
      background-color: #fff;

      .rule {
        display: grid;
        grid-template-columns: 2fr 1fr 2fr auto;
        gap: 12px;
        align-items: center;

        .field,
        .operator,
        .value {
          .query-select,
          .query-input {
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;

            &:focus {
              outline: none;
              border-color: #2196f3;
              box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
            }
          }
        }

        .rule-controls {
          .query-btn {
            padding: 6px 12px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            background-color: #f44336;
            color: white;
            font-size: 12px;

            &:hover {
              background-color: #da190b;
            }
          }
        }
      }
    }
  }
}

Custom Operators

import { Injectable } from "@angular/core";
import { OperatorsService } from "ngx-ntk-query-builder";

@Injectable()
export class CustomOperatorsService extends OperatorsService {
  // Add custom operators
  private readonly customOperators = {
    string: ["custom_operator_1", "custom_operator_2"],
    integer: ["custom_numeric_operator"],
  };

  private readonly customOperatorsDisplayNames = {
    custom_operator_1: "Custom Operator 1",
    custom_operator_2: "Custom Operator 2",
    custom_numeric_operator: "Custom Numeric Operator",
  };

  getOperators(rule: QueryRule, fieldMap: QueryBuilderFieldMap): string[] {
    const baseOperators = super.getOperators(rule, fieldMap);
    const customOperators = this.customOperators[rule.type] || [];
    return [...baseOperators, ...customOperators];
  }

  getOperatorDisplayName(operator: string): string {
    return this.customOperatorsDisplayNames[operator] || super.getOperatorDisplayName(operator);
  }
}

🌐 Internationalization

Translation Setup

// Translation keys for query builder
const queryBuilderTranslations = {
  en: {
    querybuilder: {
      And: "AND",
      Or: "OR",
      AddRule: "Add Rule",
      AddGroup: "Add Group",
      Remove: "Remove",
      equal: "Equals",
      not_equal: "Does not equal",
      contains: "Contains",
      not_contains: "Does not contain",
      begins_with: "Begins with",
      not_begins_with: "Does not begin with",
      ends_with: "Ends with",
      not_ends_with: "Does not end with",
      greater: "Greater than",
      greater_or_equal: "Greater than or equal",
      less: "Less than",
      less_or_equal: "Less than or equal",
      is_null: "Is null",
      is_not_null: "Is not null",
      is_empty: "Is empty",
      is_not_empty: "Is not empty",
      in: "In",
      not_in: "Not in",
      Yes: "Yes",
      No: "No",
    },
  },
  fa: {
    querybuilder: {
      And: "و",
      Or: "یا",
      AddRule: "افزودن قانون",
      AddGroup: "افزودن گروه",
      Remove: "حذف",
      equal: "مساوی",
      not_equal: "مساوی نیست",
      contains: "شامل",
      not_contains: "شامل نیست",
      begins_with: "شروع می‌شود با",
      not_begins_with: "شروع نمی‌شود با",
      ends_with: "پایان می‌یابد با",
      not_ends_with: "پایان نمی‌یابد با",
      greater: "بزرگتر از",
      greater_or_equal: "بزرگتر یا مساوی",
      less: "کمتر از",
      less_or_equal: "کمتر یا مساوی",
      is_null: "خالی است",
      is_not_null: "خالی نیست",
      is_empty: "تهی است",
      is_not_empty: "تهی نیست",
      in: "در",
      not_in: "در نیست",
      Yes: "بله",
      No: "خیر",
    },
  },
};

🔒 Validation & Error Handling

Query Validation

// Custom query validation
validateQuery(query: QueryRuleSet): boolean {
  if (!query.rules || query.rules.length === 0) {
    return false;
  }

  return query.rules.every(rule => {
    if (this.isQueryRuleSet(rule)) {
      return this.validateQuery(rule);
    } else {
      return this.validateRule(rule);
    }
  });
}

private validateRule(rule: QueryRule): boolean {
  return rule.field && rule.type && rule.operator;
}

private isQueryRuleSet(rule: any): rule is QueryRuleSet {
  return 'condition' in rule && 'rules' in rule;
}

Error Handling

@Component({
  template: `
    <ngx-ntk-query-builder [fieldMap]="fieldMap" [(data)]="queryData" (queryChange)="onQueryChange($event)" (validationError)="onValidationError($event)"> </ngx-ntk-query-builder>

    <div *ngIf="validationError" class="error-message">
      {{ validationError }}
    </div>
  `,
})
export class QueryBuilderWithValidation {
  validationError: string = "";

  onQueryChange(query: QueryRuleSet): void {
    if (this.validateQuery(query)) {
      this.validationError = "";
      // Process valid query
    } else {
      this.validationError = "Invalid query structure";
    }
  }

  onValidationError(error: any): void {
    console.error("Query validation error:", error);
    this.validationError = "Query validation failed";
  }
}

🧪 Testing

Unit Tests

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NgxNtkQueryBuilderModule } from "ngx-ntk-query-builder";

describe("QueryBuilderComponent", () => {
  let component: QueryBuilderComponent;
  let fixture: ComponentFixture<QueryBuilderComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [NgxNtkQueryBuilderModule],
      declarations: [QueryBuilderComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(QueryBuilderComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it("should create", () => {
    expect(component).toBeTruthy();
  });

  it("should emit query change event", () => {
    const mockQuery: QueryRuleSet = {
      condition: "and",
      rules: [
        {
          field: "name",
          type: "string",
          operator: "contains",
          value: "test",
        },
      ],
    };
    spyOn(component.queryChange, "emit");

    component.onQueryChange(mockQuery);

    expect(component.queryChange.emit).toHaveBeenCalledWith(mockQuery);
  });

  it("should validate query structure", () => {
    const validQuery: QueryRuleSet = {
      condition: "and",
      rules: [
        {
          field: "name",
          type: "string",
          operator: "contains",
          value: "test",
        },
      ],
    };

    expect(component.validateQuery(validQuery)).toBe(true);
  });
});

Integration Tests

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NgxNtkQueryBuilderModule } from "ngx-ntk-query-builder";

describe("QueryBuilder Integration", () => {
  let fixture: ComponentFixture<QueryBuilderComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [NgxNtkQueryBuilderModule],
      declarations: [QueryBuilderComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(QueryBuilderComponent);
  });

  it("should display query builder interface", () => {
    fixture.detectChanges();

    const queryBuilder = fixture.nativeElement.querySelector(".ngx-ntk-query-builder");
    expect(queryBuilder).toBeTruthy();
  });

  it("should add rule when add rule button is clicked", () => {
    fixture.detectChanges();

    const addRuleButton = fixture.nativeElement.querySelector(".query-btn-add");
    addRuleButton.click();
    fixture.detectChanges();

    const rules = fixture.nativeElement.querySelectorAll(".rule-wrap");
    expect(rules.length).toBeGreaterThan(0);
  });

  it("should change condition when condition switcher is clicked", () => {
    fixture.detectChanges();

    const orCondition = fixture.nativeElement.querySelector('input[value="or"]');
    orCondition.click();
    fixture.detectChanges();

    expect(fixture.componentInstance.queryData.condition).toBe("or");
  });
});

⚡ Performance

Optimization Tips

// Use OnPush change detection for better performance
@Component({
  selector: "app-query-builder",
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class QueryBuilderComponent {
  // Implement trackBy function for ngFor
  trackByRule(index: number, rule: QueryRule | QueryRuleSet): string {
    return rule.field || `rule-${index}`;
  }

  // Debounce query changes
  private queryChangeSubject = new Subject<QueryRuleSet>();

  ngOnInit(): void {
    this.queryChangeSubject
      .pipe(
        debounceTime(300),
        distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr))
      )
      .subscribe((query) => {
        this.handleQueryChange(query);
      });
  }

  onQueryChange(query: QueryRuleSet): void {
    this.queryChangeSubject.next(query);
  }
}

Memory Management

// Proper cleanup
ngOnDestroy(): void {
  this.queryChangeSubject.complete();
  this.destroy$.next();
  this.destroy$.complete();
}

// Optimize field map updates
updateFieldMap(newFieldMap: QueryBuilderFieldMap): void {
  if (JSON.stringify(this.fieldMap) !== JSON.stringify(newFieldMap)) {
    this.fieldMap = newFieldMap;
    this.cdr.markForCheck();
  }
}

📝 Examples

Basic Query Builder

@Component({
  selector: "app-basic-query-builder",
  template: ` <ngx-ntk-query-builder [fieldMap]="fieldMap" [(data)]="queryData" (queryChange)="onQueryChange($event)"> </ngx-ntk-query-builder> `,
})
export class BasicQueryBuilderComponent {
  fieldMap: QueryBuilderFieldMap = {
    name: { name: "Name", type: "string" },
    age: { name: "Age", type: "integer" },
    email: { name: "Email", type: "string" },
  };

  queryData: QueryRuleSet = {
    condition: "and",
    rules: [],
  };

  onQueryChange(query: QueryRuleSet): void {
    console.log("Query:", query);
  }
}

Advanced Query Builder with Custom Fields

@Component({
  selector: "app-advanced-query-builder",
  template: `
    <ngx-ntk-query-builder [fieldMap]="fieldMap" [settings]="settings" [(data)]="queryData" (queryChange)="onQueryChange($event)"> </ngx-ntk-query-builder>

    <div class="query-preview">
      <h3>Generated Query:</h3>
      <pre>{{ queryPreview }}</pre>
    </div>
  `,
  styles: [
    `
      .query-preview {
        margin-top: 20px;
        padding: 16px;
        background-color: #f5f5f5;
        border-radius: 8px;

        pre {
          background-color: #fff;
          padding: 12px;
          border-radius: 4px;
          overflow-x: auto;
        }
      }
    `,
  ],
})
export class AdvancedQueryBuilderComponent {
  fieldMap: QueryBuilderFieldMap = {
    name: {
      name: "Name",
      type: "string",
      settings: {
        nullableOperators: true,
        emptyOperators: true,
      },
    },
    age: {
      name: "Age",
      type: "integer",
    },
    status: {
      name: "Status",
      type: "select",
      options: [
        { name: "Active", value: "active" },
        { name: "Inactive", value: "inactive" },
        { name: "Pending", value: "pending" },
      ],
    },
    birthDate: {
      name: "Birth Date",
      type: "date",
    },
    isActive: {
      name: "Is Active",
      type: "boolean",
    },
  };

  settings = {
    allowRuleSets: true,
  };

  queryData: QueryRuleSet = {
    condition: "and",
    rules: [],
  };

  get queryPreview(): string {
    return JSON.stringify(this.queryData, null, 2);
  }

  onQueryChange(query: QueryRuleSet): void {
    console.log("Advanced query:", query);
    // Generate SQL or other query format
    const sqlQuery = this.generateSQL(query);
    console.log("SQL Query:", sqlQuery);
  }

  private generateSQL(query: QueryRuleSet): string {
    // Implement SQL generation logic
    return "SELECT * FROM users WHERE " + this.buildWhereClause(query);
  }

  private buildWhereClause(query: QueryRuleSet): string {
    // Implement WHERE clause building logic
    return "";
  }
}

Query Builder with Database Integration

@Component({
  selector: "app-database-query-builder",
  template: `
    <ngx-ntk-query-builder [fieldMap]="fieldMap" [(data)]="queryData" (queryChange)="onQueryChange($event)"> </ngx-ntk-query-builder>

    <div class="query-actions">
      <button mat-raised-button color="primary" (click)="executeQuery()">Execute Query</button>
      <button mat-button (click)="saveQuery()">Save Query</button>
    </div>

    <div *ngIf="queryResults" class="query-results">
      <h3>Results ({{ queryResults.length }} records)</h3>
      <table mat-table [dataSource]="queryResults">
        <ng-container matColumnDef="name">
          <th mat-header-cell *matHeaderCellDef>Name</th>
          <td mat-cell *matCellDef="let element">{{ element.name }}</td>
        </ng-container>
        <ng-container matColumnDef="age">
          <th mat-header-cell *matHeaderCellDef>Age</th>
          <td mat-cell *matCellDef="let element">{{ element.age }}</td>
        </ng-container>
        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
        <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
      </table>
    </div>
  `,
})
export class DatabaseQueryBuilderComponent {
  fieldMap: QueryBuilderFieldMap = {
    name: { name: "Name", type: "string" },
    age: { name: "Age", type: "integer" },
    email: { name: "Email", type: "string" },
  };

  queryData: QueryRuleSet = {
    condition: "and",
    rules: [],
  };

  queryResults: any[] = [];
  displayedColumns = ["name", "age"];

  constructor(private dataService: DataService) {}

  onQueryChange(query: QueryRuleSet): void {
    console.log("Query changed:", query);
  }

  executeQuery(): void {
    const sqlQuery = this.generateSQL(this.queryData);
    this.dataService.executeQuery(sqlQuery).subscribe(
      (results) => {
        this.queryResults = results;
      },
      (error) => {
        console.error("Query execution failed:", error);
      }
    );
  }

  saveQuery(): void {
    this.dataService.saveQuery(this.queryData).subscribe(
      () => {
        console.log("Query saved successfully");
      },
      (error) => {
        console.error("Failed to save query:", error);
      }
    );
  }

  private generateSQL(query: QueryRuleSet): string {
    // Implement SQL generation logic
    return "SELECT * FROM users WHERE 1=1";
  }
}

🔄 Version History

v20.25.5

  • Initial release with core functionality
  • Visual query builder interface
  • Multiple data type support
  • Nested conditions support
  • Multi-language support
  • Custom operators system

v20.25.4

  • Bug fixes and performance improvements
  • Enhanced validation system
  • Improved accessibility features
  • Better mobile responsiveness

🤝 Contributing

We welcome contributions! Please see our contributing guidelines:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Submit a pull request

📄 License

This project is licensed under the ISC License.

🆘 Support

For support and questions:

  • Create an issue on GitHub
  • Contact: ntk.ir

👨‍💻 Author

Alireza Karavi


Note: This library is part of the NTK CMS Angular Libraries collection. For more information, see the main project README.