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
Maintainers
Readme
NG Data Grid Library
A powerful, feature-rich Angular data grid component built with Angular 17+ and Angular Material
📋 Table of Contents
- Overview
- Features
- Installation
- Basic Usage
- Configuration Options
- Column Management
- Column Reorder Modal
- Button/Action Column System
- Data Services
- Export Functionality
- Filtering & Search
- API Reference
- Examples
- Pagination Options
- 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
- Standard Pagination - Angular Material's
✅ 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-gridInstall Dependencies
npm install @angular/material @angular/cdk @angular/animationsImportant: 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 elevationstroked- Outlined buttonsflat- Flat buttons without backgroundfab- Floating action buttonsmini-fab- Mini floating action buttons
🎨 Color Themes
primary- Primary theme coloraccent- Accent theme colorwarn- 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 rightCustom 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 classComponent 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 changesSee 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 dataStandard 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 fieldGrid 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-testDemo 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 controlsKey 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 DataGridServiceSolution: 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
