A powerful, feature-rich Angular 20+ data grid component with advanced template support, action columns, sorting, filtering, pagination, virtual scrolling, and export capabilities. Built with standalone components and modern Angular architecture.
npm install @ng-vui/ng-vui-grid
Peer Dependencies:
@angular/core, @angular/common, @angular/forms)Built with modern Angular patterns:
No NgModules required
Reactive state management
Modular filtering, editing, export capabilities
Dedicated services for different concerns
Full TypeScript interfaces and types
import {
NgVuiGridComponent,
NgVuiGridColumnDirective,
GridOptions,
DEFAULT_GRID_OPTIONS
} from '@ng-vui/ng-vui-grid';
@Component({
selector: 'app-example',
standalone: true,
imports: [NgVuiGridComponent, NgVuiGridColumnDirective],
template: `
<ng-vui-grid
[rowData]="data"
[gridOptions]="gridOptions"
(gridReady)="onGridReady($event)"
>
<!-- Regular column -->
<ng-template ngVuiGridColumn="name" headerName="Full Name" [width]="200" let-value>
<span class="font-medium">{{ value }}</span>
</ng-template>
<!-- Action column -->
<ng-template ngVuiGridColumn="actions" headerName="Actions" [width]="150" [sortable]="false" let-row="row">
<button (click)="edit(row)" class="btn btn-sm btn-primary mr-2">Edit</button>
<button (click)="delete(row)" class="btn btn-sm btn-danger">Delete</button>
</ng-template>
</ng-vui-grid>
`
})
export class ExampleComponent {
data = [
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'Active' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'Inactive' }
];
gridOptions: GridOptions = {
...DEFAULT_GRID_OPTIONS,
pagination: true,
paginationPageSize: 20,
enableSorting: true,
enableFiltering: true,
enableSelection: true,
header: {
showTitle: true,
title: 'User Management',
showControls: true,
showColumnChooser: true,
showExport: true
}
};
onGridReady(event: GridReadyEvent) {
console.log('Grid is ready:', event);
}
edit(row: any) {
console.log('Edit:', row);
}
delete(row: any) {
console.log('Delete:', row);
}
}
import { NgGridComponent, ColumnDefinition } from '@ertpl-ui/ang-grid';
@Component({
template: `
<ng-grid
[columnDefs]="columns"
[rowData]="data"
[gridOptions]="gridOptions"
></ng-grid>
`
})
export class ProgrammaticExample {
columns: ColumnDefinition[] = [
{ field: 'name', headerName: 'Full Name', width: 200, sortable: true },
{ field: 'email', headerName: 'Email', width: 250, filterable: true },
{ field: 'status', headerName: 'Status', width: 120 }
];
data = [...]; // Your data array
gridOptions = {...}; // Your grid options
}
Templates receive the following context variables:
interface TemplateContext {
$implicit: any; // The cell value
value: any; // The cell value (same as $implicit)
row: RowData; // The entire row data
column: ColumnDefinition; // Column definition
rowIndex: number; // Row index
}
<ng-template ngVuiGridColumn="user" headerName="User Info" let-value let-row="row" let-rowIndex="rowIndex">
<div class="flex items-center">
<img [src]="row.avatar" class="w-8 h-8 rounded-full mr-3">
<div>
<div class="font-medium">{{ value }}</div>
<div class="text-sm text-gray-500">Row #{{ rowIndex + 1 }}</div>
</div>
</div>
</ng-template>
| Property | Type | Default | Description |
|---|---|---|---|
rowData |
RowData[] |
[] |
Array of data objects |
columnDefs |
ColumnDefinition[] |
[] |
Column definitions (programmatic) |
gridOptions |
GridOptions |
{} |
Grid configuration options |
loading |
boolean |
false |
Show loading indicator |
loadingTemplate |
TemplateRef |
- | Custom loading template |
noDataTemplate |
TemplateRef |
- | Custom no data template |
| Event | Type | Description |
|---|---|---|
gridReady |
GridReadyEvent |
Fired when grid is initialized |
cellClicked |
CellClickedEvent |
Fired when cell is clicked |
rowSelected |
RowSelectedEvent |
Fired when row selection changes |
sortChanged |
SortEvent[] |
Fired when sorting changes |
filterChanged |
FilterEvent |
Fired when filtering changes |
interface ColumnDefinition {
field: string; // Data field name
headerName?: string; // Column header text
width?: number; // Fixed width in pixels
minWidth?: number; // Minimum width
maxWidth?: number; // Maximum width
sortable?: boolean; // Enable sorting
filterable?: boolean; // Enable filtering
resizable?: boolean; // Allow column resizing
visible?: boolean; // Show/hide column
pinned?: 'left' | 'right'; // Pin column to left/right
cellTemplate?: TemplateRef<any>; // Custom cell template
headerTemplate?: TemplateRef<any>; // Custom header template
valueFormatter?: (params: any) => string; // Format cell value
cellClass?: string | string[]; // CSS classes for cell
headerClass?: string | string[]; // CSS classes for header
}
interface GridOptions {
// Pagination
pagination?: boolean; // Enable pagination
paginationPageSize?: number; // Rows per page
paginationPageSizes?: number[]; // Available page sizes
// Sorting & Filtering
enableSorting?: boolean; // Enable sorting
enableFiltering?: boolean; // Enable filtering
multiSort?: boolean; // Allow multi-column sorting
// Selection
enableSelection?: boolean; // Enable row selection
selectionMode?: 'single' | 'multiple'; // Selection mode
showSelectionCheckbox?: boolean; // Show selection checkboxes
// Virtual Scrolling
virtualScrolling?: boolean; // Enable virtual scrolling
rowHeight?: number; // Fixed row height
// Header Options
header?: {
showTitle?: boolean; // Show grid title
title?: string; // Grid title text
showControls?: boolean; // Show header controls
exportControls?: { // Export options
enabled?: boolean;
showCsv?: boolean;
showExcel?: boolean;
showPdf?: boolean;
};
columnControls?: { // Column options
enabled?: boolean;
showColumnSelector?: boolean;
};
};
// Styling
cssClasses?: {
root?: string; // Grid container classes
header?: string; // Header classes
body?: string; // Body classes
row?: string; // Row classes
cell?: string; // Cell classes
};
}
<ng-template ngVuiGridColumn="actions" headerName="Actions" [sortable]="false" let-row="row" let-rowIndex="rowIndex">
<div class="relative">
<button (click)="toggleMenu(rowIndex)" class="btn btn-sm">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01"/>
</svg>
</button>
@if (openMenuIndex === rowIndex) {
<div class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10">
<button (click)="edit(row)" class="block w-full text-left px-4 py-2 hover:bg-gray-100">Edit</button>
<button (click)="duplicate(row)" class="block w-full text-left px-4 py-2 hover:bg-gray-100">Duplicate</button>
<button (click)="delete(row)" class="block w-full text-left px-4 py-2 hover:bg-gray-100 text-red-600">Delete</button>
</div>
}
</div>
</ng-template>
<ng-template ngVuiGridColumn="status" headerName="Status" let-value>
<span [class]="getStatusClass(value)" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium">
{{ value }}
</span>
</ng-template>
getStatusClass(status: string): string {
const classes = {
'Active': 'bg-green-100 text-green-800',
'Pending': 'bg-yellow-100 text-yellow-800',
'Inactive': 'bg-red-100 text-red-800'
};
return classes[status] || 'bg-gray-100 text-gray-800';
}
<ng-template ngVuiGridColumn="progress" headerName="Progress" let-value>
<div class="flex items-center space-x-2">
<div class="flex-1 bg-gray-200 rounded-full h-2">
<div class="bg-violet-600 h-2 rounded-full transition-all" [style.width.%]="value"></div>
</div>
<span class="text-sm text-gray-600">{{ value }}%</span>
</div>
</ng-template>
<ng-template ngVuiGridColumn="user" headerName="User" let-row="row">
<div class="flex items-center space-x-3">
<img [src]="row.avatar || '/default-avatar.png'" [alt]="row.name" class="w-10 h-10 rounded-full">
<div>
<div class="font-medium text-gray-900">{{ row.name }}</div>
<div class="text-sm text-gray-500">{{ row.email }}</div>
</div>
</div>
</ng-template>
// Component method
export() {
this.gridApi?.exportDataAsCsv({
fileName: 'data-export.csv',
columnKeys: ['name', 'email', 'status'],
skipHeaders: false
});
}
// Excel export
exportExcel() {
this.gridApi?.exportDataAsExcel({
fileName: 'data-export.xlsx',
sheetName: 'Data'
});
}
// PDF export
exportPdf() {
this.gridApi?.exportDataAsPdf({
fileName: 'data-export.pdf',
title: 'Data Report'
});
}
The component uses Tailwind CSS by default. You can customize the appearance:
gridOptions: GridOptions = {
cssClasses: {
root: 'rounded-lg border border-gray-200',
header: 'bg-gray-50 border-b',
body: 'bg-white',
row: 'hover:bg-gray-50 border-b border-gray-100',
cell: 'px-4 py-3'
}
};
/* Custom grid styling */
.my-grid {
--grid-border-color: #e5e7eb;
--grid-header-bg: #f9fafb;
--grid-row-hover: #f3f4f6;
}
.my-grid .grid-header {
background: var(--grid-header-bg);
}
.my-grid .grid-row:hover {
background: var(--grid-row-hover);
}
@Component({
template: `
<ng-grid [rowData]="formData" [gridOptions]="options">
<ng-template ngVuiGridColumn="isActive" headerName="Active" let-value let-row="row">
<input type="checkbox" [checked]="value" (change)="updateField(row, 'isActive', $event.target.checked)">
</ng-template>
</ng-grid>
`
})
export class FormGridExample {
updateField(row: any, field: string, value: any) {
const index = this.formData.indexOf(row);
this.formData[index][field] = value;
// Update your reactive form here
}
}
@Component({
template: `
<ng-grid
[rowData]="data$ | async"
[loading]="loading"
[gridOptions]="options"
(sortChanged)="onSort($event)"
(filterChanged)="onFilter($event)"
></ng-grid>
`
})
export class ServerDataExample {
data$ = this.dataService.getData();
loading = false;
onSort(sorts: SortEvent[]) {
this.loading = true;
this.data$ = this.dataService.getData({ sort: sorts }).pipe(
finalize(() => this.loading = false)
);
}
onFilter(filter: FilterEvent) {
this.loading = true;
this.data$ = this.dataService.getData({ filter }).pipe(
finalize(() => this.loading = false)
);
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NgGridComponent } from '@ertpl-ui/ang-grid';
describe('NgGridComponent', () => {
let component: NgGridComponent;
let fixture: ComponentFixture<NgGridComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgGridComponent]
});
fixture = TestBed.createComponent(NgGridComponent);
component = fixture.componentInstance;
});
it('should render data correctly', () => {
component.rowData = [{ id: 1, name: 'Test' }];
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Test');
});
});
git checkout -b feature/my-featuregit commit -am 'Add my feature'git push origin feature/my-featureng build issues with ng-packagrMIT ยฉ VUI
For detailed information about specific features, see these comprehensive guides:
@ng-vui/multi-select - Multi-select dropdown component@ng-vui/date-picker - Date picker component@ng-vui/select-input - Single select dropdown@ng-vui/text-input - Text input component@ng-vui/textarea - Textarea component@ng-vui/auto-complete - Auto complete component