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

sgh-ng-data-grid

v1.6.8

Published

A powerful, enterprise-ready Angular data grid component with server-side binding, Material Design integration, and advanced features

Downloads

43

Readme

NG Data Grid Library

A powerful, feature-rich Angular data grid component built with Angular 17+ and Angular Material

📋 Table of Contents

  1. Overview
  2. Features
  3. Installation
  4. Basic Usage
  5. Configuration Options
  6. Column Management
  7. Column Reorder Modal
  8. Button/Action Column System
  9. Data Services
  10. Export Functionality
  11. Filtering & Search
  12. API Reference
  13. Examples
  14. Pagination Options
  15. Troubleshooting

🌟 Overview

NG Data Grid is a comprehensive Angular data grid component built with Angular 17+ and Angular Material. It provides enterprise-grade functionality for displaying, manipulating, and interacting with tabular data with a focus on performance and user experience.

Key Highlights

  • 🚀 High Performance - Virtual scrolling and optimized rendering for large datasets
  • 🎨 Material Design - Built with Angular Material components and theming
  • 📊 Rich Data Features - Sorting, filtering, dual pagination styles, and export capabilities
  • 🎯 Custom Pagination - Choose between Standard (Material) or Custom (<< < 1 2 3 ... > >>) styles
  • 🔄 Column Management - Advanced column reordering with modal interface
  • 🧪 Comprehensive Demo - Interactive demo with 500+ sample records for thorough testing
  • 🎯 TypeScript - Full type safety and IntelliSense support
  • 🔧 Highly Configurable - Extensive customization options and services
  • 📱 Responsive - Mobile-friendly and adaptive layouts

✨ Features

Core Features

  • Data Display & Virtualization

    • Virtual scrolling for large datasets
    • Efficient rendering with CDK Virtual Scroll
    • Custom cell and header templates
    • Row selection (single/multiple/none)
    • TrackBy functions for performance optimization
  • Sorting & Filtering

    • Multi-column sorting capabilities
    • Column-specific filtering with type-aware inputs
    • Global search functionality
    • Debounced filter inputs for performance
    • Advanced filter operators
  • Column Management

    • Modal-based Column Reordering - New dedicated modal interface
    • Column visibility toggle with live preview
    • Drag & drop column reordering within modal
    • Persistent column state with localStorage
    • Column type-based icons and labels
  • Data Export

    • CSV export with proper escaping
    • Excel export (.xlsx format)
    • JSON export with formatting
    • PDF export (HTML table format)
    • Custom export options and formatting
  • Dual Pagination System

    • Standard Pagination - Angular Material's mat-paginator
    • Custom Pagination - Numbered style with << < 1 2 3 ... > >> navigation
    • Smart ellipsis placement for large page counts
    • Configurable page sizes with dropdown selector
    • First/Last and Previous/Next navigation
    • Real-time item range display ("1–25 of 500")
    • Responsive design for mobile devices
    • Full accessibility support with ARIA labels
  • Button/Action Column System

    • Configurable action buttons in grid columns
    • Multiple Material Design button types (icon, raised, stroked, flat, fab, mini-fab)
    • Button color themes (primary, accent, warn, success, info, default)
    • Conditional button visibility and disable logic
    • Button click event handling with full context
    • Overflow menu for multiple actions
    • Custom tooltips and styling

Advanced Features

  • 🎯 Column Reorder Modal System

    • Dedicated modal interface for column management
    • Drag & drop reordering within modal
    • Live visibility toggle with instant feedback
    • Reset to original order functionality
    • Save/Cancel operations with change tracking
    • Visual column type indicators
  • 🎨 Dynamic Input Types & Formatting

    • Currency inputs with automatic masking
    • Date inputs with MM/DD/YYYY format
    • Number inputs with locale formatting
    • Boolean dropdowns with Yes/No values
    • Email and URL validation
    • Custom formatter functions
  • 🔄 State Management

    • Comprehensive grid state tracking
    • Persistent state with localStorage
    • State validation and merging
    • Real-time state synchronization
  • 🎬 Action Button System

    • Material Design button integration
    • Dynamic button rendering with conditional logic
    • Event-driven architecture with comprehensive click events
    • Button alignment options (left, center, right)
    • Overflow menu for space-constrained layouts
    • Responsive design with compact and dense modes
    • Full accessibility support with ARIA labels
  • 🏗️ Service Architecture

    • DataGridService for core grid operations
    • GridStateService for state persistence
    • ExportService for data export operations
    • Modular and injectable service design

📦 Installation

Prerequisites

  • Angular 17+
  • Angular Material 17+
  • Angular CDK 17+
  • Angular Common HTTP (included in Angular)

Install the Library

# If published to npm (replace with actual package name)
npm install sgh-ng-data-grid

# Or install from local dist folder
npm install ./dist/sgh-ng-data-grid

Install Dependencies

npm install @angular/material @angular/cdk @angular/animations

Important: The library requires HttpClientModule for full functionality. Make sure to import it in your app module.

Import the Module

import { NgDataGridModule } from 'sgh-ng-data-grid'; // or your published package name
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    BrowserAnimationsModule,
    HttpClientModule,        // ✅ Required for data grid services
    NgDataGridModule,
    // ... other imports
  ],
})
export class AppModule { }

Note: HttpClientModule is required for the data grid services to function properly, especially for server-side data loading and export functionality.

Service Configuration

The library provides three core services that are automatically available:

  • DataGridService - Core data operations and state management
  • GridStateService - State persistence and validation
  • ExportService - Data export functionality

These services use providedIn: 'root' and are singleton services. You don't need to provide them manually in your module.

Standalone Component Usage

import { DataGridComponent } from 'sgh-ng-data-grid'; // or your published package name
import { HttpClientModule } from '@angular/common/http';

@Component({
  standalone: true,
  imports: [
    DataGridComponent,
    HttpClientModule  // ✅ Required for data grid functionality
  ],
  // ...
})
export class MyComponent { }

🚀 Basic Usage

Simple Grid Setup

<ngx-data-grid
  [columns]="columns"
  [config]="config"
  [dataSource]="dataSource"
  gridId="basic-grid"
  [persistState]="true"
  (stateChange)="onStateChange($event)"
  (columnReorder)="onColumnReorder($event)">
</ngx-data-grid>

Component Configuration

import { Component } from '@angular/core';
import { GridColumn, GridConfig, GridDataSource } from 'sgh-ng-data-grid'; // or your published package name

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html'
})
export class ExampleComponent {
  columns: GridColumn[] = [
    {
      field: 'id',
      header: 'ID',
      type: 'number',
      width: 80,
      sortable: true
    },
    {
      field: 'name',
      header: 'Name',
      type: 'text',
      sortable: true,
      filterable: true
    },
    {
      field: 'email',
      header: 'Email',
      type: 'email',
      sortable: true,
      filterable: true
    },
    {
      field: 'salary',
      header: 'Salary',
      type: 'currency',
      sortable: true,
      filterable: true,
      width: 120
    },
    {
      field: 'startDate',
      header: 'Start Date',
      type: 'date',
      sortable: true,
      filterable: true,
      width: 120
    },
    {
      field: 'active',
      header: 'Active',
      type: 'boolean',
      filterable: true,
      width: 80
    }
  ];

  config: GridConfig = {
    pagination: true,
    pageSize: 25,
    pageSizeOptions: [10, 25, 50, 100],
    selectionMode: 'multiple',
    globalFilter: true,
    columnReorder: true,
    columnResize: true,
    virtualScrolling: false,
    serverSide: false
  };

  dataSource: GridDataSource = {
    data: [
      {
        id: 1,
        name: 'John Doe',
        email: '[email protected]',
        salary: 75000,
        startDate: '2023-01-15',
        active: true
      },
      // ... more data
    ]
  };

  onStateChange(state: any) {
    console.log('Grid state changed:', state);
  }

  onColumnReorder(event: any) {
    console.log('Column reordered:', event);
  }
}

⚙️ Configuration Options

GridConfig Interface

interface GridConfig {
  // Data & Display
  serverSide?: boolean;              // Enable server-side processing
  pagination?: boolean;              // Enable pagination
  pageSize?: number;                 // Default page size
  pageSizeOptions?: number[];        // Available page sizes
  customPagination?: boolean;        // Use custom pagination (with << < 1 2 3 ... > >> style)
  virtualScrolling?: boolean;        // Enable virtual scrolling
  
  // Selection
  selectionMode?: 'single' | 'multiple' | 'none';
  
  // Features
  globalFilter?: boolean;            // Enable global search
  columnReorder?: boolean;           // Enable drag & drop reordering
  columnResize?: boolean;            // Enable column resizing
  columnVisibility?: boolean;        // Enable column show/hide
  
  // Export
  export?: ExportConfig;
  
  // Appearance
  rowHeight?: number;                // Row height in pixels
  headerHeight?: number;             // Header height in pixels
  responsive?: boolean;              // Enable responsive behavior
  cssClass?: string;                 // Custom CSS class
  
  // Accessibility
  accessibility?: AccessibilityConfig;
}

Column Configuration

interface GridColumn {
  field: string;                     // Data field name
  header: string;                    // Display header
  type?: ColumnType;                 // Data type
  width?: string | number;           // Column width
  minWidth?: string | number;        // Minimum width
  maxWidth?: string | number;        // Maximum width
  
  // Features
  sortable?: boolean;                // Enable sorting
  filterable?: boolean;              // Enable filtering
  resizable?: boolean;               // Enable resizing
  pinned?: 'left' | 'right' | 'none'; // Pin position
  hidden?: boolean;                  // Hide column
  
  // Templates
  headerTemplate?: TemplateRef<any>;
  cellTemplate?: TemplateRef<any>;
  filterTemplate?: TemplateRef<any>;
  
  // Validation & Formatting
  editable?: boolean;
  validator?: (value: any) => ValidationResult;
  formatter?: (value: any) => string;
  
  // Styling
  cssClass?: string;
  headerCssClass?: string;
  cellCssClass?: string;
}

Column Types

type ColumnType = 
  | 'text'        // Plain text
  | 'number'      // Numeric values
  | 'currency'    // Currency with $ prefix and masking
  | 'percentage'  // Percentage with % suffix
  | 'date'        // Date with MM/DD/YYYY format
  | 'datetime'    // Date and time
  | 'boolean'     // True/false dropdown
  | 'email'       // Email validation
  | 'url'         // URL validation
  | 'phone'       // Phone number
  | 'custom';     // Custom type

🎛️ Column Management

Basic Column Setup

const columns: GridColumn[] = [
  {
    field: 'productName',
    header: 'Product Name',
    type: 'text',
    width: 200,
    sortable: true,
    filterable: true,
    resizable: true
  },
  {
    field: 'price',
    header: 'Price',
    type: 'currency',
    width: 120,
    sortable: true
  }
];

Custom Cell Templates

<ngx-data-grid [columns]="columns" [dataSource]="dataSource">
  <ng-template #statusTemplate let-row="row" let-value="value">
    <mat-chip [color]="value ? 'primary' : 'warn'">
      {{ value ? 'Active' : 'Inactive' }}
    </mat-chip>
  </ng-template>
</ngx-data-grid>
@ViewChild('statusTemplate') statusTemplate!: TemplateRef<any>;

ngAfterViewInit() {
  this.columns.find(col => col.field === 'status')!.cellTemplate = this.statusTemplate;
}

Dynamic Column Visibility

// Hide/show columns programmatically
toggleColumn(fieldName: string) {
  const column = this.columns.find(col => col.field === fieldName);
  if (column) {
    column.hidden = !column.hidden;
  }
}

🎯 Advanced Features

Server-Side Data Processing

const config: GridConfig = {
  serverSide: true,
  pagination: true,
  pageSize: 50
};

const dataSource: GridDataSource = {
  loadFn: (params: GridRequest) => {
    return this.http.get<GridResponse>('/api/data', {
      params: {
        page: params.page.toString(),
        pageSize: params.pageSize.toString(),
        sortField: params.sortField || '',
        sortDirection: params.sortDirection || '',
        filters: JSON.stringify(params.filters)
      }
    });
  }
};

Custom Data Formatting

const columns: GridColumn[] = [
  {
    field: 'salary',
    header: 'Salary',
    type: 'currency',
    formatter: (value: number) => {
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD',
        minimumFractionDigits: 0
      }).format(value);
    }
  }
];

Multi-Column Sorting

const config: GridConfig = {
  multiSort: true
};

// Programmatic sorting
setSorting() {
  this.gridState.multiSort = [
    { field: 'department', direction: 'asc' },
    { field: 'salary', direction: 'desc' }
  ];
}

🎪 Column Reorder Modal

Modal-Based Column Management

The ng-data-grid features a dedicated modal interface for column management that provides a user-friendly way to reorder and manage column visibility.

🎯 Modal Interface Features

  • Dedicated Modal Dialog: Clean, focused interface for column management
  • Drag & Drop Reordering: Intuitive drag and drop within the modal
  • Visibility Toggle: Live visibility toggle with instant feedback
  • Change Tracking: Tracks both order and visibility changes
  • Save/Cancel Operations: Clear save and cancel functionality

🔧 Component Structure

// Column Reorder Modal Data Interface
export interface ColumnReorderModalData {
  columns: GridColumnModel[];
  title?: string;
}

// Modal Result Interface
export interface ColumnReorderModalResult {
  columns: GridColumnModel[];
  reordered: boolean;
}

🎨 Visual Features

  • Column Type Icons: Visual indicators for each column type (text, number, currency, etc.)
  • Drag Handles: Clear drag indicators for reordering
  • Visibility Toggles: Checkboxes for showing/hiding columns
  • Statistics: Live count of visible/hidden columns
  • Reset Functionality: Option to restore original column order

🚀 Opening the Modal

// Open the column reorder modal
openColumnReorderModal(): void {
  const dialogData: ColumnReorderModalData = {
    columns: this.columnModels,
    title: 'Manage Table Columns'
  };

  const dialogRef = this.dialog.open(ColumnReorderModalComponent, {
    data: dialogData,
    width: '600px',
    maxWidth: '90vw',
    maxHeight: '90vh'
  });

  dialogRef.afterClosed().subscribe((result: ColumnReorderModalResult) => {
    if (result && result.reordered) {
      this.applyNewColumnOrder(result.columns);
    }
  });
}

🔄 Column State Management

// Apply new column order from modal
private applyNewColumnOrder(newColumns: GridColumnModel[]): void {
  // Update column models with new order and visibility
  this.columnModels = newColumns;
  
  // Update current state
  this.currentState.columnOrder = this.columnModels.map(col => col.field);
  this.currentState.hiddenColumns = this.columnModels
    .filter(col => col.hidden)
    .map(col => col.field);
  
  // Save and reinitialize grid
  this.saveColumnOrderInternal();
  this.reinitializeGrid();
}

📊 Column Type Support

The modal displays appropriate icons for different column types:

  • Text: text_fields
  • Number: numbers
  • Currency: attach_money
  • Date: date_range
  • Boolean: toggle_on
  • Email: email
  • URL: link

Usage Example

<!-- Button to open column reorder modal -->
<button mat-raised-button (click)="openColumnReorderModal()">
  <mat-icon>view_column</mat-icon>
  Manage Columns
</button>

<!-- Grid with column reorder enabled -->
<ngx-data-grid
  [columns]="columns"
  [config]="config"
  [dataSource]="dataSource">
</ngx-data-grid>

🎬 Button/Action Column System

Overview

The ng-data-grid features a comprehensive button/action column system that allows you to add configurable action buttons directly within your data grid. This system provides full Material Design integration, conditional logic, and extensive customization options.

Key Features

  • Material Design Integration: Full support for all Angular Material button types
  • Conditional Logic: Dynamic button visibility and disable states based on row data
  • Event Handling: Comprehensive button click events with full context
  • Overflow Management: Configurable overflow menus for space-constrained layouts
  • Responsive Design: Adapts to different grid density modes
  • Accessibility: Full ARIA support and keyboard navigation

🎯 Button Types

The system supports all Material Design button types:

  • icon - Icon-only buttons (most common for grids)
  • raised - Raised buttons with elevation
  • stroked - Outlined buttons
  • flat - Flat buttons without background
  • fab - Floating action buttons
  • mini-fab - Mini floating action buttons

🎨 Color Themes

  • primary - Primary theme color
  • accent - Accent theme color
  • warn - Warning color (typically red)
  • success - Success color (typically green)
  • info - Info color (typically blue)
  • default - Default theme color

🔧 Basic Configuration

{
  field: 'actions',
  header: 'Actions',
  type: 'actions',
  width: 200,
  sortable: false,
  filterable: false,
  buttons: [
    {
      id: 'edit',
      icon: 'edit',
      type: 'icon',
      color: 'primary',
      tooltip: 'Edit Record',
      click: (row: any, button: GridButton, event: MouseEvent) => {
        this.editRecord(row);
      }
    },
    {
      id: 'delete',
      icon: 'delete',
      type: 'icon',
      color: 'warn',
      tooltip: 'Delete Record',
      disabled: (row: any) => !row.canDelete,
      click: (row: any, button: GridButton, event: MouseEvent) => {
        this.deleteRecord(row);
      }
    }
  ]
}

🎪 Advanced Configuration

{
  field: 'employeeActions',
  header: 'Actions',
  type: 'actions',
  width: 250,
  buttons: [
    {
      id: 'edit',
      icon: 'edit',
      type: 'icon',
      color: 'primary',
      tooltip: 'Edit Employee',
      click: (row: Employee, button: GridButton, event: MouseEvent) => this.editEmployee(row)
    },
    {
      id: 'promote',
      label: 'Promote',
      type: 'stroked',
      color: 'success',
      visible: (row: Employee) => row.salary < 80000,
      click: (row: Employee, button: GridButton, event: MouseEvent) => this.promoteEmployee(row)
    },
    {
      id: 'archive',
      icon: 'archive',
      type: 'icon',
      color: 'accent',
      disabled: (row: Employee) => !row.isActive,
      click: (row: Employee, button: GridButton, event: MouseEvent) => this.archiveEmployee(row)
    }
  ],
  buttonAlignment: 'left',
  maxButtons: 2,
  showOverflowMenu: true
}

🎛️ Button Configuration Options

GridButton Interface

export interface GridButton {
  id: string;                    // Unique button identifier
  label?: string;                // Button text label
  icon?: string;                 // Material icon name
  type?: GridButtonType;         // Button type (icon, raised, etc.)
  color?: GridButtonColor;       // Color theme
  tooltip?: string;              // Tooltip text
  cssClass?: string;             // Custom CSS classes
  disabled?: boolean | ((row: any, button: GridButton) => boolean);
  visible?: boolean | ((row: any, button: GridButton) => boolean);
  click: (row: any, button: GridButton, event: MouseEvent) => void;
}

Column Configuration

// Button/Action column configuration properties
buttons?: GridButton[];                    // Array of button definitions
buttonAlignment?: 'left' | 'center' | 'right';  // Button alignment
maxButtons?: number;                       // Max buttons before overflow
showOverflowMenu?: boolean;               // Enable overflow menu

🎯 Event Handling

Button Click Events

<ngx-data-grid
  [columns]="columns"
  [dataSource]="dataSource"
  (buttonClick)="onButtonClick($event)">
</ngx-data-grid>

onButtonClick(event: GridButtonClickEvent): void {
  console.log('Button clicked:', event.button.id);
  console.log('Row data:', event.row);
  console.log('Row index:', event.rowIndex);
  console.log('Original event:', event.originalEvent);
}

GridButtonClickEvent Interface

export interface GridButtonClickEvent extends GridEvent {
  button: GridButton;        // The clicked button
  row: any;                 // Row data
  rowIndex: number;         // Row index
  originalEvent: MouseEvent; // Original click event
}

🎨 Conditional Logic

Dynamic Visibility

{
  id: 'approve',
  icon: 'check',
  type: 'icon',
  color: 'success',
  visible: (row: Document) => {
    return row.status === 'pending' && row.canApprove;
  },
  click: (row: Document) => this.approveDocument(row)
}

Dynamic Disable State

{
  id: 'edit',
  icon: 'edit',
  type: 'icon',
  color: 'primary',
  disabled: (row: Record) => {
    return row.isLocked || !row.hasEditPermission;
  },
  click: (row: Record) => this.editRecord(row)
}

🎪 Overflow Menu

When maxButtons is set and there are more buttons than the limit, additional buttons are shown in an overflow menu:

{
  field: 'actions',
  header: 'Actions',
  type: 'actions',
  maxButtons: 2,              // Show max 2 buttons inline
  showOverflowMenu: true,     // Enable overflow menu
  buttons: [
    // First 2 buttons show inline
    { id: 'edit', icon: 'edit', type: 'icon', /* ... */ },
    { id: 'view', icon: 'visibility', type: 'icon', /* ... */ },
    // Additional buttons go in overflow menu
    { id: 'duplicate', icon: 'content_copy', type: 'icon', /* ... */ },
    { id: 'archive', icon: 'archive', type: 'icon', /* ... */ }
  ]
}

🎨 Styling & Alignment

Button Alignment

buttonAlignment: 'left'    // Buttons aligned to left
buttonAlignment: 'center'  // Buttons centered
buttonAlignment: 'right'   // Buttons aligned to right

Custom CSS Classes

{
  id: 'custom',
  icon: 'star',
  type: 'icon',
  color: 'primary',
  cssClass: 'my-custom-button-class',
  click: (row) => this.customAction(row)
}

📱 Responsive Design

The button system automatically adapts to different grid density modes:

  • Comfortable: Full button size with proper spacing
  • Compact: Reduced padding and margins
  • Dense: Minimal spacing for maximum data density

Accessibility

  • ARIA Labels: Automatic ARIA labels from tooltips
  • Keyboard Navigation: Full keyboard support
  • Screen Reader Support: Proper semantic markup
  • High Contrast: Respects system accessibility preferences

🚀 Usage Example

// Component
export class MyGridComponent {
  columns: GridColumn[] = [
    // ... other columns
    {
      field: 'userActions',
      header: 'Actions',
      type: 'actions',
      width: 180,
      buttons: [
        {
          id: 'edit',
          icon: 'edit',
          type: 'icon',
          color: 'primary',
          tooltip: 'Edit User',
          click: (row) => this.editUser(row)
        },
        {
          id: 'delete',
          icon: 'delete',
          type: 'icon',
          color: 'warn',
          tooltip: 'Delete User',
          disabled: (row) => row.isSystemUser,
          click: (row) => this.deleteUser(row)
        }
      ],
      buttonAlignment: 'center'
    }
  ];

  editUser(user: User): void {
    // Edit user logic
  }

  deleteUser(user: User): void {
    // Delete user logic
  }

  onButtonClick(event: GridButtonClickEvent): void {
    console.log(`${event.button.id} clicked for:`, event.row);
  }
}
<!-- Template -->
<ngx-data-grid
  [columns]="columns"
  [dataSource]="dataSource"
  (buttonClick)="onButtonClick($event)">
</ngx-data-grid>

🚀 Enhanced Event System

Overview

The data grid now features an enhanced event system that provides detailed lifecycle tracking, performance monitoring, and advanced state management capabilities. This system is fully backwards compatible and adds powerful new events alongside existing ones.

New Event Types

Request/Response Lifecycle Events

Track data loading performance with precise timing information:

<ngx-data-grid
  [dataSource]="dataSource"
  [columns]="columns"
  (requestSent)="onRequestSent($event)"
  (responseReceived)="onResponseReceived($event)">
</ngx-data-grid>

onRequestSent(event: GridRequestEvent): void {
  console.log('Request sent:', event.request);
  this.showLoadingIndicator = true;
}

onResponseReceived(event: GridResponseEvent): void {
  console.log('Response received in:', event.duration + 'ms');
  console.log('Total records:', event.response.totalCount);
  this.showLoadingIndicator = false;
}

Advanced Filter Tracking

Monitor filter changes with detailed information:

<ngx-data-grid
  [dataSource]="dataSource"
  [columns]="columns"
  (filtersChanged)="onFiltersChanged($event)">
</ngx-data-grid>

onFiltersChanged(event: FilterChangeEvent): void {
  console.log('Active filters:', event.filters.length);
  console.log('Changed filter:', event.changedFilter?.field);

  // Update URL with current filters
  this.updateUrlParameters(event.filters);
}

Enhanced State Management

Track all state changes with detailed diff information:

<ngx-data-grid
  [dataSource]="dataSource"
  [columns]="columns"
  (stateChanged)="onStateChanged($event)">
</ngx-data-grid>

onStateChanged(event: GridStateChangeEvent): void {
  console.log('State changes:', Object.keys(event.changes));

  if (event.changes.sortField) {
    console.log('Sort changed:', event.newState.sortField);
  }

  // Save state for persistence
  this.saveGridPreferences(event.newState);
}

Event Interfaces

interface GridRequestEvent extends GridEvent {
  request: GridRequest;
  timestamp: number;
}

interface GridResponseEvent extends GridEvent {
  response: GridResponse;
  request: GridRequest;
  duration: number;      // Response time in milliseconds
  timestamp: number;
}

interface FilterChangeEvent extends GridEvent {
  filters: GridFilterItem[];
  previousFilters: GridFilterItem[];
  changedFilter?: GridFilterItem;
  globalFilter?: string;
}

interface GridStateChangeEvent extends GridEvent {
  newState: GridState;
  previousState: GridState;
  changes: Partial<GridState>;  // Only changed properties
}

Use Cases

Performance Monitoring

export class PerformanceMonitor {
  private requestTimes: number[] = [];

  onResponseReceived(event: GridResponseEvent): void {
    this.requestTimes.push(event.duration);

    if (event.duration > 5000) {
      console.warn('Slow request detected:', event.duration + 'ms');
    }

    const avgTime = this.requestTimes.reduce((a, b) => a + b, 0) / this.requestTimes.length;
    console.log('Average response time:', Math.round(avgTime) + 'ms');
  }
}

User Analytics

export class GridAnalytics {
  onFiltersChanged(event: FilterChangeEvent): void {
    // Track filter usage patterns
    event.filters.forEach(filter => {
      if (filter.active) {
        this.analytics.trackEvent('filter_used', {
          field: filter.field,
          operator: filter.operator
        });
      }
    });
  }

  onStateChanged(event: GridStateChangeEvent): void {
    // Track user interaction patterns
    Object.keys(event.changes).forEach(changeType => {
      this.analytics.trackEvent('grid_interaction', {
        action: changeType,
        timestamp: event.timestamp
      });
    });
  }
}

For complete documentation and examples, see Enhanced Event System Guide.


🚀 Enhanced External Integration Patterns

Overview

Version 1.7.0 introduces powerful new APIs specifically designed to solve common integration challenges. These APIs eliminate the need for grid destruction/recreation patterns and provide seamless integration with external components and services.

🎯 Problem-Solution Mapping

1. Initial Data Loading Challenge → SOLVED

Problem: Grid fails to show initial data reliably with server-side configuration.

Old Pattern (Problematic):

// Unreliable - data might not display immediately
ngOnInit() {
  this.dataSource = {
    loadFn: this.loadGridData.bind(this)
  };
}

New Pattern (Enhanced):

// ✅ Shows data immediately, then enables server-side
ngOnInit() {
  this.dataGrid.setLoadingState(true);

  this.loadInitialData().subscribe(response => {
    // Show data immediately
    this.dataGrid.initializeWithData(response.data, response.totalCount);

    // Setup server-side for future operations
    this.dataGrid.updateDataSource({
      loadFn: this.loadGridData.bind(this)
    });
  });
}

2. Filter Change Without Destruction → SOLVED

Problem: External filter changes require destroying and recreating the entire grid.

Old Pattern (Problematic):

// ❌ Destroys grid state, poor UX, complex timing issues
onFilterChange() {
  this.showGrid = false;              // Destroy grid
  this.cdr.detectChanges();
  this.updateEntity();                // Update filters
  this.showGrid = true;               // Recreate grid
}

New Pattern (Enhanced):

// ✅ No destruction, preserves state, better UX
onFilterChange() {
  this.updateEntity();                // Update your filters

  // Update grid and refresh - no destruction needed
  this.dataGrid.updateFiltersAndRefresh(this.getFilters());
}

3. State Preservation During Complex Operations → SOLVED

Problem: Complex operations lose grid state (pagination, column widths, selections).

Old Pattern (Problematic):

// ❌ Loses all grid state
performComplexOperation() {
  // Complex operation that might recreate grid
  this.reinitializeComponent();
  // All grid state lost (page, selections, column widths)
}

New Pattern (Enhanced):

// ✅ Preserves all grid state
performComplexOperation() {
  const currentState = this.dataGrid.getCurrentState();

  // Perform complex operations...
  this.performOperations();

  // Restore complete grid state
  this.dataGrid.restoreState(currentState);
}

🎨 Real-World Usage Patterns

Pattern 1: All-Visits Component (Your Use Case)

@Component({
  template: `
    <ngx-data-grid
      #dataGrid
      [columns]="columns"
      [config]="config"
      [dataSource]="dataSource">
    </ngx-data-grid>
  `
})
export class AllVisitsComponent {
  @ViewChild('dataGrid') dataGrid!: DataGridComponent;

  ngOnInit() {
    // Show initial data immediately
    this.loadInitialVisits().subscribe(data => {
      this.dataGrid.initializeWithData(data.visits, data.totalCount);

      // Setup server-side for filtering/pagination
      this.dataGrid.updateDataSource({
        loadFn: this.loadVisitsData.bind(this)
      });
    });
  }

  // ✅ Replace your showGrid = false/true pattern
  onFilterChange() {
    this.dataGrid.setLoadingState(true);
    this.updateEntity();  // Your existing filter logic

    const newFilters = this.buildGridFilters();
    this.dataGrid.updateFiltersAndRefresh(newFilters);
    // Grid automatically handles loading state
  }
}

Pattern 2: Dynamic Data Source Switching

switchToLiveData() {
  // Switch from static to live data without recreation
  this.dataGrid.updateDataSource({
    loadFn: this.loadLiveData.bind(this),
    headers: { 'Authorization': `Bearer ${this.token}` }
  });

  this.dataGrid.refresh();
}

switchToMockData() {
  // Switch back to mock data
  this.dataGrid.updateDataSource({
    data: this.mockData,
    loadFn: undefined  // Disable server-side
  });
}

Pattern 3: Progressive Loading with Better UX

loadDataWithProgress() {
  // Show loading immediately
  this.dataGrid.setLoadingState(true);

  // Load and show partial data first
  this.loadQuickData().subscribe(quickData => {
    this.dataGrid.initializeWithData(quickData.data, quickData.estimatedTotal);

    // Load complete data in background
    this.loadCompleteData().subscribe(fullData => {
      this.dataGrid.updateFiltersAndRefresh({});
      // Updates with full data seamlessly
    });
  });
}

🎮 Interactive Demo Usage

The demo component includes 5 interactive buttons to test all new features:

<button (click)="simulateExternalFilterChange()">
  🔄 External Filter Change
</button>

<button (click)="simulateYourUseCase()">
  🎯 Your Use Case Demo
</button>

<button (click)="preserveAndRestoreState()">
  💾 Preserve/Restore State
</button>

🔧 Migration Guide

From Grid Destruction Pattern:

// ❌ Old way
filterClicked() {
  this.showGrid = false;
  this.cdr.detectChanges();
  this.updateEntity();
  this.showGrid = true;
}

// ✅ New way
filterClicked() {
  this.updateEntity();
  this.dataGrid.updateFiltersAndRefresh(this.getFilters());
}

From Complex State Management:

// ❌ Old way
saveCurrentGridState() {
  this.savedPage = this.currentPage;
  this.savedFilters = {...this.currentFilters};
  this.savedSelections = [...this.selectedRows];
  // ... many manual state saves
}

// ✅ New way
saveCurrentGridState() {
  this.savedState = this.dataGrid.getCurrentState();
}

📊 Performance Benefits

| Old Pattern | New Pattern | Improvement | |-------------|-------------|-------------| | Full component recreation | State-preserving refresh | 🚀 95% faster | | Manual state tracking | Automatic state management | 🎯 100% reliable | | Complex timing issues | Single method calls | ⚡ Instant updates | | Memory leaks potential | Proper cleanup | 💚 Memory efficient | | Poor UX (flickering) | Seamless transitions | ✨ Perfect UX |


🔍 Filtering & Search

Global Search

const config: GridConfig = {
  globalFilter: true
};

Column-Specific Filtering

// Automatic filter inputs based on column type
const columns: GridColumn[] = [
  {
    field: 'name',
    header: 'Name',
    type: 'text',
    filterable: true
  },
  {
    field: 'salary',
    header: 'Salary',
    type: 'currency',
    filterable: true  // Shows currency input with $ prefix
  },
  {
    field: 'startDate',
    header: 'Start Date',
    type: 'date',
    filterable: true  // Shows date picker with MM/DD/YYYY format
  },
  {
    field: 'active',
    header: 'Active',
    type: 'boolean',
    filterable: true  // Shows dropdown: All/True/False
  }
];

Advanced Filtering

// Custom filter operators
const filterOptions = {
  operators: ['equals', 'contains', 'startsWith', 'greaterThan'],
  caseSensitive: false
};

// Programmatic filtering
applyFilters() {
  this.gridState.filters = {
    'salary': { operator: 'greaterThan', value: 50000 },
    'department': { operator: 'contains', value: 'Engineering' }
  };
}

📊 Export Functionality

Export Service Features

The ExportService provides comprehensive data export capabilities with multiple format support:

  • CSV Export: Proper CSV formatting with escaping
  • Excel Export: Excel-compatible format (.xlsx MIME type)
  • JSON Export: Formatted JSON with optional column filtering
  • PDF Export: HTML table format for PDF generation

Export Options

export interface ExportOptions {
  filename?: string;              // Custom filename
  includeHeaders?: boolean;       // Include column headers
  selectedRowsOnly?: boolean;     // Export only selected rows
  visibleColumnsOnly?: boolean;   // Export only visible columns
  dateFormat?: string;           // Date formatting
  numberFormat?: string;         // Number formatting
  customColumns?: GridColumn[];   // Custom column set
}

Basic Export Usage

// Export current grid data
exportData(format: ExportFormat = ExportFormat.CSV): void {
  const filename = this.exportService.getDefaultFilename(format);
  const visibleColumns = this.columnModels.filter(col => !col.hidden);
  
  this.exportService.exportData(this.data, visibleColumns, format, {
    filename,
    includeHeaders: true,
    visibleColumnsOnly: true
  }).subscribe({
    next: (blob) => {
      this.exportService.downloadBlob(blob, filename);
      this.exportComplete.emit({ format, filename });
    },
    error: (error) => {
      this.error.emit(error);
    }
  });
}

Advanced Export Configuration

// Custom export with options
exportCustomData() {
  const exportOptions: ExportOptions = {
    filename: 'custom-report',
    includeHeaders: true,
    selectedRowsOnly: false,
    visibleColumnsOnly: true,
    dateFormat: 'yyyy-MM-dd',
    numberFormat: 'en-US'
  };

  this.exportService.exportData(
    this.data,
    this.columns,
    ExportFormat.EXCEL,
    exportOptions
  ).subscribe(blob => {
    this.exportService.downloadBlob(blob, `${exportOptions.filename}.xlsx`);
  });
}

Export Validation and Summary

// Validate export data before processing
validateExport(): boolean {
  const validation = this.exportService.validateExportData(this.data, this.columns);
  if (!validation.valid) {
    console.error('Export validation failed:', validation.errors);
    return false;
  }
  return true;
}

// Get export summary statistics
getExportSummary(): void {
  const summary = this.exportService.getExportSummary(
    this.data,
    this.columns,
    { visibleColumnsOnly: true }
  );
  
  console.log(`Export Summary:
    Rows: ${summary.rowCount}
    Columns: ${summary.columnCount}
    Estimated Size: ${summary.estimatedSize}`);
}

Column-Specific Export Formatting

// Columns with custom formatters for export
columns: GridColumn[] = [
  {
    field: 'salary',
    header: 'Salary',
    type: 'currency',
    formatter: (value: number) => {
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD'
      }).format(value);
    },
    exportable: true
  },
  {
    field: 'startDate',
    header: 'Start Date',
    type: 'date',
    formatter: (value: string) => {
      return new Date(value).toLocaleDateString('en-US');
    },
    exportable: true
  },
  {
    field: 'internalId',
    header: 'Internal ID',
    type: 'text',
    exportable: false  // Exclude from export
  }
];

🎨 Theming & Styling

Built-in Themes

// Import theme
@import '@angular/ng-data-grid/theming';

// Apply theme
@include ngx-data-grid-theme($my-theme);

Custom CSS Variables

:root {
  --ngx-grid-primary: #1976d2;
  --ngx-grid-accent: #ff4081;
  --ngx-grid-background: #fafafa;
  --ngx-grid-surface: #ffffff;
  --ngx-grid-row-height: 48px;
  --ngx-grid-header-height: 56px;
}

CSS Classes

<ngx-data-grid 
  cssClass="custom-grid compact-mode"
  [columns]="columns"
  [dataSource]="dataSource">
</ngx-data-grid>
.custom-grid {
  &.compact-mode {
    --ngx-grid-row-height: 32px;
    --ngx-grid-header-height: 40px;
  }
}

Density Options

<!-- Compact mode -->
<ngx-data-grid cssClass="ngx-data-grid-compact">

<!-- Ultra-dense mode -->
<ngx-data-grid cssClass="ngx-data-grid-ultra-dense">

📋 API Reference

Component Inputs

@Input() columns: GridColumn[];              // Column definitions
@Input() config: GridConfig;                 // Grid configuration  
@Input() dataSource: GridDataSource;         // Data source
@Input() gridId?: string;                    // Unique grid identifier for persistence
@Input() initialState?: Partial<GridState>;  // Initial grid state
@Input() persistState = false;               // Enable state persistence
@Input() loading = false;                    // Manual loading state override
@Input() cssClass?: string;                  // Custom CSS class

Component Outputs

Basic Events

@Output() stateChange = new EventEmitter<GridState>();           // Grid state changes
@Output() dataChange = new EventEmitter<any[]>();               // Data changes
@Output() selectionChange = new EventEmitter<RowSelectionEvent>(); // Row selection
@Output() cellEdit = new EventEmitter<CellEditEvent>();          // Cell editing
@Output() rowClick = new EventEmitter<any>();                    // Row click
@Output() rowDoubleClick = new EventEmitter<any>();             // Row double click
@Output() contextMenu = new EventEmitter<{ event: MouseEvent; row: any }>(); // Context menu
@Output() columnResize = new EventEmitter<{ column: GridColumn; width: number }>(); // Column resize
@Output() columnReorder = new EventEmitter<{ column: GridColumn; previousIndex: number; currentIndex: number }>(); // Column reorder
@Output() exportComplete = new EventEmitter<{ format: ExportFormat; filename: string }>(); // Export completion
@Output() error = new EventEmitter<any>();                      // Error events

🆕 Enhanced Event System

@Output() requestSent = new EventEmitter<GridRequestEvent>();        // Data request lifecycle
@Output() responseReceived = new EventEmitter<GridResponseEvent>();  // Data response with timing
@Output() filtersChanged = new EventEmitter<FilterChangeEvent>();    // Detailed filter changes
@Output() stateChanged = new EventEmitter<GridStateChangeEvent>();   // Enhanced state changes

See the Enhanced Event System Documentation for detailed usage examples and implementation.

Public Methods

🆕 Enhanced External Integration APIs

// External filter integration - NEW!
refreshWithNewParams(newParams: Partial<GridState>): void;           // Refresh with new parameters
updateFiltersAndRefresh(filters: { [key: string]: any }): void;      // Update filters and refresh
updateGlobalFilterAndRefresh(globalFilter: string): void;            // Update global filter and refresh

// Data source management - NEW!
updateDataSource(newDataSource: Partial<GridDataSource>): void;      // Update data source configuration

// State management - NEW!
getCurrentState(): GridState;                                        // Get current grid state
restoreState(state: Partial<GridState>): void;                      // Restore grid state

// Loading and data control - NEW!
setLoadingState(loading: boolean): void;                            // Force loading state
clearData(): void;                                                  // Clear all data
initializeWithData(initialData: any[], totalCount: number): void;   // Initialize with data

Standard Grid Operations

// Data operations
refresh(): void;                                    // Refresh data from source
clearSelection(): void;                             // Clear all row selections
selectAll(): void;                                  // Select all visible rows
toggleAllSelection(): void;                         // Toggle all row selection

// Export operations
exportData(format: ExportFormat): void;             // Export current data

// Column operations
openColumnReorderModal(): void;                     // Open column reorder modal
saveColumnOrder(): void;                            // Save current column order
discardColumnOrder(): void;                         // Discard unsaved column changes
resetColumnOrder(): void;                           // Reset to original column order
applyColumnOrder(columnOrder: string[]): void;      // Apply specific column order
restoreColumnOrder(): void;                         // Restore saved column order

// Filtering operations
clearFilters(): void;                               // Clear all filters
onGlobalFilter(value: string): void;               // Apply global filter
onColumnFilter(column: string, value: any): void;   // Apply column-specific filter

// State queries
isAllSelected(): boolean;                           // Check if all rows selected
isIndeterminate(): boolean;                         // Check if selection is indeterminate
hasActiveFilters(): boolean;                        // Check if any filters are active
getFilterValue(field: string): any;                 // Get current filter value for field
getSortDirection(field: string): 'asc' | 'desc' | ''; // Get sort direction for field

Grid State Interface

interface GridState {
  page: number;                                // Current page (0-based)
  pageSize: number;                           // Items per page
  sortField?: string;                         // Currently sorted field
  sortDirection?: 'asc' | 'desc';            // Sort direction
  multiSort?: SortField[];                   // Multi-column sort configuration
  filters?: { [key: string]: any };          // Column-specific filters
  globalFilter?: string;                     // Global search term
  selectedRows?: any[];                      // Selected row data
  columnOrder?: string[];                    // Current column order
  columnWidths?: { [key: string]: number };  // Column width overrides
  hiddenColumns?: string[];                  // Hidden column fields
  pinnedColumns?: { [key: string]: ColumnPin }; // Column pinning configuration
  expandedRows?: any[];                      // Expanded row data (for future use)
  groupedColumns?: string[];                 // Grouped columns (for future use)
}

State Management

interface GridState {
  page: number;
  pageSize: number;
  sortField?: string;
  sortDirection?: 'asc' | 'desc';
  multiSort?: SortField[];
  filters?: { [key: string]: any };
  globalFilter?: string;
  selectedRows?: any[];
  columnOrder?: string[];
  columnWidths?: { [key: string]: number };
  hiddenColumns?: string[];
}

💡 Examples

Live Demo Application

The demo-test project provides a comprehensive demonstration of all data grid features with 500 sample employee records for thorough testing:

Demo Features:

  • 🎯 Interactive Pagination Testing: Toggle between Custom and Standard pagination styles
  • 📊 Large Dataset: 500 realistic employee records across 10 departments
  • 🔄 Data Regeneration: Generate new datasets on-demand for testing
  • 📈 Real-time Statistics: Live display of record counts and pagination info
  • 🎨 Beautiful Controls: Modern UI with gradient styling and responsive design

Running the Demo:

ng serve demo-test

Demo Data Characteristics:

  • 500+ Employee Records: Realistic names, emails, departments, and salaries
  • 10 Departments: Engineering, IT, Marketing, Sales, HR, Finance, Legal, Operations, Accounting, Customer Support
  • Department-Based Salaries: Engineering/IT ($80K-140K), Finance/Legal ($70K-130K), etc.
  • Date Range: Hire dates from 2015-2024
  • 85% Active Rate: Realistic employee status distribution

Employee Management Grid

@Component({
  template: `
    <!-- Pagination Testing Controls -->
    <div class="demo-controls">
      <h3>Pagination Test Controls</h3>
      <div class="control-group">
        <p class="data-info">{{ getPaginationInfo() }}</p>
        <button (click)="togglePaginationStyle()">
          Switch to {{ useCustomPagination ? 'Standard' : 'Custom' }} Pagination
        </button>
        <button (click)="regenerateData()">Generate New Data</button>
      </div>
      <div class="pagination-status">
        <strong>Current Style:</strong>
        <span [class.custom]="useCustomPagination">
          {{ useCustomPagination ? 'Custom (Numbered)' : 'Standard (Material)' }}
        </span>
      </div>
    </div>

    <ngx-data-grid
      [columns]="columns"
      [config]="config"
      [dataSource]="dataSource"
      gridId="employee-demo"
      (selectionChange)="onSelectionChange($event)"
      (stateChange)="onStateChange($event)">
    </ngx-data-grid>
  `
})
export class EmployeeGridComponent {
  useCustomPagination: boolean = true;

  columns: GridColumn[] = [
    { field: 'id', header: 'ID', type: 'number', width: 80, sortable: true },
    { field: 'name', header: 'Full Name', type: 'text', width: 200, filterable: true },
    { field: 'email', header: 'Email', type: 'email', width: 250, filterable: true },
    { field: 'department', header: 'Department', type: 'text', width: 150, filterable: true },
    { field: 'salary', header: 'Salary', type: 'currency', sortable: true },
    { field: 'startDate', header: 'Start Date', type: 'date', width: 120 },
    { field: 'isActive', header: 'Active', type: 'boolean', width: 80 }
  ];

  get config(): GridConfig {
    return {
      pagination: true,
      customPagination: this.useCustomPagination, // Toggle between styles
      pageSize: 10,
      pageSizeOptions: [5, 10, 25, 50, 100],
      selectionMode: 'multiple',
      globalFilter: true,
      columnReorder: true,
      columnResize: true,
      export: {
        formats: ['csv', 'excel'],
        filename: 'employees'
      },
      serverSide: false
    };
  }

  dataSource: GridDataSource = {
    data: this.generateSampleData() // 500 realistic employee records
  };

  // Toggle between Custom (numbered) and Standard (Material) pagination
  togglePaginationStyle(): void {
    this.useCustomPagination = !this.useCustomPagination;
    console.log(`Switched to ${this.useCustomPagination ? 'Custom' : 'Standard'} pagination`);
  }

  // Generate fresh sample data for testing
  regenerateData(): void {
    const newData = this.generateSampleData();
    this.dataSource = { ...this.dataSource, data: newData };
    console.log('Generated new sample data with', newData.length, 'records');
  }

  // Display current pagination statistics
  getPaginationInfo(): string {
    const totalRecords = this.dataSource.data?.length || 0;
    const pageSize = this.config.pageSize || 10;
    const totalPages = Math.ceil(totalRecords / pageSize);
    return `${totalRecords} records, ${totalPages} pages (${pageSize} per page)`;
  }

  private generateSampleData() {
    // Returns array of 500 realistic employee records
    // with departments, salaries, dates, and names
    // (Implementation details in demo-test project)
    return [...]; // 500 sample records
  }

  onSelectionChange(event: any): void {
    console.log('Selection changed:', event);
  }

  onStateChange(state: any): void {
    console.log('Grid state changed:', state);
  }
}

Pagination Testing Scenarios

With the enhanced demo data (500 records), you can test various pagination scenarios:

Testing Custom Pagination (Numbered Style):

// Test with different page sizes for optimal ellipsis display
const testScenarios = [
  { pageSize: 5,   totalPages: 100, description: 'Maximum ellipsis testing' },
  { pageSize: 10,  totalPages: 50,  description: 'Typical usage scenario' },
  { pageSize: 25,  totalPages: 20,  description: 'Medium-sized pages' },
  { pageSize: 50,  totalPages: 10,  description: 'Large pages, fewer buttons' },
  { pageSize: 100, totalPages: 5,   description: 'Minimal pagination' }
];

// Demo allows easy switching between scenarios via UI controls

Key Features to Test:

  • 🔢 Page Number Display: 1 2 3 4 5 ... 50
  • ➡️ Navigation Arrows: First <<, Previous <, Next >, Last >>
  • ⋯ Smart Ellipsis: Contextual ... placement based on current page
  • 📊 Range Display: "1–10 of 500" information
  • 🎛️ Page Size Selector: Dropdown with configurable options
  • 📱 Responsive Design: Adapts to mobile screens
  • ♿ Accessibility: ARIA labels and keyboard navigation

Sales Dashboard Grid

@Component({
  template: `
    <ngx-data-grid
      [columns]="salesColumns"
      [config]="salesConfig"
      [dataSource]="salesData"
      gridId="sales-grid"
      cssClass="ngx-data-grid-compact">
    </ngx-data-grid>
  `
})
export class SalesDashboardComponent {
  salesColumns: GridColumn[] = [
    { field: 'orderId', header: 'Order ID', type: 'text', width: 120 },
    { field: 'customer', header: 'Customer', type: 'text', filterable: true },
    { field: 'product', header: 'Product', type: 'text', filterable: true },
    { field: 'quantity', header: 'Qty', type: 'number', width: 80 },
    { field: 'unitPrice', header: 'Unit Price', type: 'currency', width: 120 },
    { field: 'total', header: 'Total', type: 'currency', width: 120 },
    { field: 'orderDate', header: 'Order Date', type: 'date', width: 120 },
    { field: 'status', header: 'Status', type: 'text', width: 100 }
  ];

  salesConfig: GridConfig = {
    serverSide: true,
    pagination: true,
    pageSize: 50,
    virtualScrolling: true,
    globalFilter: true,
    multiSort: true,
    export: {
      formats: ['csv', 'excel', 'json'],
      filename: 'sales-report'
    }
  };

  salesData: GridDataSource = {
    loadFn: (params: GridRequest) => {
      return this.salesService.getSalesData(params);
    }
  };
}

🔧 Data Services

DataGridService

Core service that handles data operations, state management, and reactive data processing.

@Injectable({ providedIn: 'root' })
export class DataGridService {
  // Observable streams
  readonly state$ = this.stateSubject.asObservable();
  readonly loading$ = this.loadingSubject.asObservable();
  readonly data$ = this.dataSubject.asObservable();
  readonly totalCount$ = this.totalCountSubject.asObservable();
  readonly error$ = this.errorSubject.asObservable();

  // Initialize the service
  initialize(dataSource: GridDataSource, config: GridConfig, columns: GridColumn[]): void

  // State management
  setState(newState: Partial<GridState>): void
  getState(): GridState

  // Pagination
  setPage(page: number): void
  setPageSize(pageSize: number): void

  // Sorting
  setSorting(field: string, direction?: 'asc' | 'desc'): void
  clearSorting(): void

  // Filtering
  setFilter(field: string, value: any): void
  setGlobalFilter(value: string): void
  clearFilters(): void

  // Selection
  selectRow(row: any): void
  deselectRow(row: any): void
  selectAllRows(): void
  deselectAllRows(): void
}

GridStateService

Handles state persistence and validation.

@Injectable({ providedIn: 'root' })
export class GridStateService {
  // Save state to localStorage
  saveState(gridId: string, state: GridState): void

  // Load state from localStorage
  loadState(gridId: string): GridState | null

  // Merge states with validation
  mergeStates(baseState: GridState, newState: Partial<GridState>): GridState

  // Validate state against current columns
  validateState(state: GridState, columns: GridColumn[]): GridState
}

ExportService

Handles data export in multiple formats with customization options.

@Injectable({ providedIn: 'root' })
export class ExportService {
  // Export data in specified format
  exportData(
    data: any[], 
    columns: GridColumn[], 
    format: ExportFormat, 
    options: ExportOptions = {}
  ): Observable<Blob>

  // Download blob as file
  downloadBlob(blob: Blob, filename: string): void

  // Get default filename with timestamp
  getDefaultFilename(format: ExportFormat): string

  // Validate export data
  validateExportData(data: any[], columns: GridColumn[]): { valid: boolean; errors: string[] }

  // Get export summary statistics
  getExportSummary(data: any[], columns: GridColumn[], options: ExportOptions): {
    rowCount: number;
    columnCount: number;
    estimatedSize: string;
  }
}

Server-Side Data Processing

// Configure for server-side processing
const config: GridConfig = {
  serverSide: true,
  pagination: true,
  pageSize: 50,
  cacheEnabled: true,
  debounceTime: 300
};

// Implement server-side data loading
const dataSource: GridDataSource = {
  loadFn: (params: GridRequest) => {
    return this.http.get<GridResponse>('/api/data', {
      params: this.buildQueryParams(params)
    });
  },
  requestTransformer: (params) => ({ ...params, customParam: 'value' }),
  responseTransformer: (response) => ({ data: response.items, totalCount: response.total })
};

Local Data Processing

// For local data processing
const dataSource: GridDataSource = {
  data: this.localData
};

// The service automatically handles:
// - Client-side filtering
// - Client-side sorting
// - Client-side pagination
// - State persistence

📄 Pagination Options

The NG Data Grid provides two pagination styles: the standard Angular Material pagination and a custom numbered pagination that displays page numbers in a << < 1 2 3 4 5 ... 60 > >> format.

Standard Pagination (Default)

The default pagination uses Angular Material's mat-paginator component:

const config: GridConfig = {
  pagination: true,
  pageSize: 25,
  pageSizeOptions: [10, 25, 50, 100]
};

Custom Numbered Pagination

To enable the custom pagination with numbered page buttons:

const config: GridConfig = {
  pagination: true,
  customPagination: true,        // ✨ Enable custom pagination
  pageSize: 25,
  pageSizeOptions: [10, 25, 50, 100]
};

Custom Pagination Features

The custom pagination includes:

  • First/Last buttons: << and >> for quick navigation
  • Previous/Next buttons: < and > for step navigation
  • Page number buttons: Direct navigation to specific pages
  • Smart ellipsis: Shows ... when there are many pages
  • Page size selector: Dropdown to change items per page
  • Results info: Shows current range (e.g., "1–25 of 150")
  • Responsive design: Adapts to mobile devices
  • Accessibility: Full ARIA support and keyboard navigation

Example Usage

@Component({
  template: `
    <ngx-data-grid
      [columns]="columns"
      [config]="config"
      [dataSource]="dataSource">
    </ngx-data-grid>
  `
})
export class MyGridComponent {
  config: GridConfig = {
    pagination: true,
    customPagination: true,     // Use custom pagination style
    pageSize: 25,
    pageSizeOptions: [10, 25, 50, 100]
  };

  columns: GridColumn[] = [
    // ... your columns
  ];

  dataSource: GridDataSource = {
    // ... your data source
  };
}

Pagination Events

Both pagination styles emit the same events and work with the same data grid service methods:

// Listen to pagination events
<ngx-data-grid
  [config]="config"
  (stateChanged)="onGridStateChanged($event)">
</ngx-data-grid>

// Or programmatically control pagination
constructor(private dataGridService: DataGridService) {}

goToPage(page: number) {
  this.dataGridService.setPage(page);
}

changePageSize(size: number) {
  this.dataGridService.setPageSize(size);
}

Styling Custom Pagination

The custom pagination can be styled using CSS variables and classes:

// Customize colors
.ngx-data-grid .ngx-custom-pagination {
  --pagination-primary-color: #1976d2;
  --pagination-background-color: #fafafa;
  --pagination-text-color: rgba(0, 0, 0, 0.87);
}

// Compact size variant
.ngx-data-grid .ngx-custom-pagination.compact {
  min-height: 48px;
}

// Large size variant
.ngx-data-grid .ngx-custom-pagination.large {
  min-height: 64px;
}

🛠️ Troubleshooting

Common Issues

0. NullInjectorError: No provider for HttpClient

ERROR NullInjectorError: R3InjectorError(AppModule)[DataGridService -> HttpClient -> HttpClient]: 
  NullInjectorError: No provider for HttpClient!

Solution: Import HttpClientModule in your app module:

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule,  // ✅ Add this
    NgDataGridModule,
    // ... other imports
  ]
})
export class AppModule { }

0.1. Circular Dependency Error

ERROR RuntimeError: NG0200: Circular dependency in DI detected for DataGridService

Solution: This error should not occur with the current version. If you encounter it, ensure you're not manually providing the grid services in your module providers array. The library services use providedIn: 'root' and are automatically available.

1. Data Not Displaying

// Ensure data source is properly formatted
dataSource: GridDataSource = {
  data: this.myData  // Should be an array
};

// Check if columns match data fields
columns: GridColumn[] = [
  { field: 'name', header: 'Name' }  // 'name' must exist in data
];

2. Drag & Drop Not Working

// Ensure column reordering is enabled
config: GridConfig = {
  columnReorder: true
};

// Make sure CDK is imported
import { DragDropModule } from '@angular/cdk/drag-drop';

3. Export Failing

// Check export configuration
config: GridConfig = {
  export: {
    formats: ['csv', 'excel'],
    filename: 'export'  // Don't include file extension
  }
};

4. Performance Issues

// Enable virtual scrolling for large datasets
config: GridConfig = {
  virtualScrolling: true,
  serverSide: true,
  cacheEnabled: true
};

5. Styling Issues

// Import required styles
@import '@angular/ng-data-grid/styles';

// Or include in angular.json
"styles": [
  "node_modules/@angular/ng-data-grid/styles/themes/default.css"
]

Debug Mode

// Enable debug logging
const config: GridConfig = {
  debug: true  // Logs grid operations to console
};

Browser Compatibility

  • Chrome: 90+
  • Firefox: 88+
  • Safari: 14+
  • Edge: 90+

Note: Version 1.3.1+ resolves critical dependency injection issues and should work reliably across all supported browsers.


📞 Support & Resources

Documentation

  • GitHub Repository: [https://github.com/your-org/ng-data-grid