
Syncfusion Angular Grid
- 213 installs
- Updated August 4, 2026
- syncfusion/angular-ui-components-skills
Use syncfusion-angular-grid for development tasks
About
syncfusion-angular-grid: A skill for development. This provides functionality for development workflows.
- syncfusion-angular-grid
Syncfusion Angular Grid by the numbers
- 213 all-time installs (skills.sh)
- +7 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,842 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/angular-ui-components-skills --skill syncfusion-angular-gridAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 213 |
|---|---|
| Last updated | August 4, 2026 |
| Repository | syncfusion/angular-ui-components-skills ↗ |
What it does
Use syncfusion-angular-grid for development tasks
Files
Syncfusion Angular Grid
A comprehensive guide to implementing powerful, feature-rich data grids using Syncfusion's Angular Grid component. This skill covers all aspects of grid functionality from basic data binding to advanced features like virtual scrolling, hierarchical data, state management, and multi-format exports.
⚠️ Security & Trust Boundary
- The Grid skill does not perform any remote data access.
- All external API interaction is handled by a separate DataManager skill outside this skill’s trust boundary.
Table of Contents
- When to Use This Skill
- TreeGrid vs. Grid: Decision Guide
- Critical API Rules & Requirements
- Feature Navigation Guide
- Quick Start Example
- Common Patterns
- Key Props & Configuration
- Common Use Cases
When to Use This Skill
Use this skill when you need to:
- Display tabular data — Load and render structured data in a table format
- Enable data operations — Sort, filter, search, and paginate large datasets
- Implement editing — Add, update, and delete records with validation
- Customize columns — Configure column properties, templates, and layouts
- Export data — Generate Excel, PDF, or print-friendly reports
- Handle advanced scenarios — Hierarchical data, virtual scrolling, state persistence
- Apply styling — Theme grids, customize appearance, and style-specific elements
- Optimize performance — Implement infinite scrolling, virtual scrolling for large datasets
TreeGrid vs. Grid: Decision Guide
| Requirement | Use TreeGrid | Use Grid |
|---|---|---|
| Data Structure | Hierarchical/Tree/Nested | Flat/Tabular |
| Parent-Child Relationships | ✅ Native support | ❌ No (use Detail Template) |
| Expand/Collapse Rows | ✅ Built-in | ❌ No |
| Nested Aggregates | ✅ Includes children | ✅ Flat only |
| Indent/Outdent Operations | ✅ Yes | ❌ No |
| Column Grouping | ❌ No (use Grid) | ✅ Yes |
| Performance (100K+ rows) | ✅Excellent | ✅ Excellent |
| Use Cases | Org charts, file systems, BoM | Orders, invoices, lists |
Critical API Rules & Requirements
Grid's inbuilt API (137+ methods, 65+ events, 95+ properties) requires strict adherence to access patterns:
⚠️ Detailed rules for proper API usage are located in the reference guides — review the sections below based on your needs:
- 📄 Properties Reference → references/grid-properties.md — 95+ configurable properties organized by category
- 📄 Methods Reference → references/grid-methods.md — 137+ methods for data manipulation, selection, sorting, filtering, and more
- 📄 Events Reference → references/grid-events.md — 65+ events for lifecycle management, user interactions, and data changes
- 📄 Programmatic Control → references/programmatic-api.md — Learn @ViewChild requirement, method parameters, async handling, and refresh patterns
- 📄 Event Handling → references/events-catalog.md — Event signatures, timing, cancellation, and render event restrictions
- 📄 Module System → references/modules.md — Service injection requirements for feature methods
- 📄 Selection → references/selection.md — Selection method prerequisites
- 📄 Backend Integration → references/adaptors.md
---
Feature Navigation Guide
Getting Started & Setup
📄 Read: references/getting-started.md
- Installation and package setup
- Basic grid initialization
- CSS imports and themes
- Minimal working example
Data Management
📄 Read: references/data-binding.md
- Local and remote data binding
- Data source configuration
- API integration
- Real-time data updates
Column Configuration
📄 Read: references/columns.md
- Column properties and types
- Column templates
- Foreign key columns
- Column rendering options
Related — Advanced Column Features: 📄 Read: references/context-menu.md (context menu for columns)
Row Management & Templates
📄 Read: references/row.md
- Row properties and events
- Row templates
- Detail row templates
- Row spanning
Selection & Interaction
📄 Read: references/selection.md
- Row, cell, and column selection
- Checkbox selection
- Selection events
- Multi-select patterns
Data Operations
📄 Read: references/filtering.md
- Filter bar and filter menu
- Excel-like filtering
- Custom filters
- Programmatic filtering
📄 Read: references/sorting.md
- Single and multi-column sorting
- Custom sort order
- Sort events
📄 Read: references/searching.md
- Global search functionality
- Search across columns
- Search algorithms
📄 Read: references/paging.md
- Pagination setup
- Page size configuration
- Page change events
Editing & Validation
📄 Read: references/editing.md
- Edit types (inline, dialog, batch)
- Edit templates and custom editors
- Built-in validators (required, min, max, pattern)
- Validation rules and error messages
- Persisting changes to server
⚠️ CRITICAL: [isPrimaryKey]="true" is required on the key column — editing silently fails without it (no error thrown).Grouping & Aggregation
📄 Read: references/grouping.md
- Grouping columns
- Lazy-load grouping
- Caption templates
📄 Read: references/aggregates.md
- Summary rows
- Custom aggregate functions
- Footer aggregates
Export & Reporting
📄 Read: references/excel-export.md
- Export to Excel formats
- Export options and customization
- Export events
📄 Read: references/pdf-export.md
- Export to PDF
- PDF templates and formatting
- Header and footer configuration
📄 Read: references/print.md
- Print functionality
- Print templates
- Print events
Styling & UI Customization
📄 Read: references/style-and-appearance.md
- Theme selection
- CSS customization
- Style specific areas (header, rows, cells)
- Custom CSS classes
📄 Read: references/toolbar.md
- Toolbar items and configuration
- Custom toolbar buttons
- Toolbar events and click handlers
📄 Read: references/clipboard.md
- Copy/paste functionality
- Clipboard events
- Clipboard selection management
Advanced Column Features
📄 Read: references/frozen.md
- Freeze columns
- Multi-column freeze
- Freeze position configuration
⚠️ Performance Warning:rowDataBoundandqueryCellInfofire on every render/scroll — NEVER make API calls inside them.
⚠️ Conflict Risk: Do NOT add both a column[template]AND arowDataBoundhandler targeting the same field — produces duplicate, conflicting styling.
Cell & Row Features
📄 Read: references/cell.md
- Cell editing and selection
- Cell templates
- Cell spanning
- Cell-level events
Context & Interaction
📄 Read: references/context-menu.md
- Context menu items
- Custom context menu
- Context menu events
- Default menu configuration
Advanced Features
📄 Read: references/scrolling.md
- Virtual scrolling for 1K–100K+ records
- Infinite scrolling for continuous loading
- Sticky headers
- Scrolling performance optimization
⚠️ CRITICAL: height is required — scrolling is silently disabled without it (no error thrown).⚠️ INCOMPATIBLE: Do NOT combineenableVirtualizationorenableInfiniteScrollingwithallowPaging— results are unpredictable.
| Rows | Mode | Key Config |
|---|---|---|
| < 1,000 | allowPaging | Page module |
| 1K – 100K | enableVirtualization + height | VirtualScroll, no paging |
| Continuous | enableInfiniteScrolling + height | InfiniteScroll |
| 100K+ grouped | enableVirtualization + lazyLoadGrouping | VirtualScroll, Group |
📄 Read: references/hierarchy-grid.md
- Master-detail grids
- Child grid configuration
- Nested/hierarchical data
- Detail row templates
- Lazy-load child data
📄 Read: references/state-management.md
- Save grid state (columns, sorting, filters)
- Restore grid state
- State persistence options
📄 Read: references/adaptive.md
- Responsive/mobile mode
- Adaptive layouts
- Touch interactions
📄 Read: references/global-local.md
- Keyboard shortcuts and navigation
- Global vs local settings
- Accessibility configurations
Module System & Architecture
📄 Read: references/modules.md
- Grid module architecture and 23+ feature services
- Service dependencies and relationships
- Module injection patterns in component decorators
- Bundle optimization via selective imports
- Performance impact of module inclusion
Data Connectivity & Adaptors
📄 Read: references/adaptors.md
- 7 adaptor types for backend integration
- UrlAdaptor, ODataV4Adaptor, WebApiAdaptor, GraphQLAdaptor
- Custom adaptors and RemoteSaveAdaptor
- Backend configuration examples (C#, Node.js)
- Request/response format specifications
- Error handling and adaptor comparison
Performance Optimization
📄 Read: references/performance.md
- Virtual scrolling for 10,000+ records
- Infinite scrolling and progressive loading
- Memory management and cleanup strategies
- Bundle size optimization
- Event debouncing and throttling
- Performance benchmarking and monitoring
Accessibility & Compliance
📄 Read: references/accessibility.md
- WCAG 2.2 and Section 508 compliance
- WAI-ARIA implementation and screen readers
- Keyboard navigation (Tab, arrows, Enter, Escape)
- Color contrast and focus management
- Accessibility testing tools (axe, NVDA, JAWS)
- Semantic HTML practices
Data Validation
📄 Read: references/validation.md
- Built-in validators (required, min, max, pattern)
- Custom validation functions
- Async validation with server checks
- Validation events and error display
- Server-side validation with ASP.NET
- Validation rules by column type
Command Column & Row Actions
📄 Read: references/command-column.md
- Built-in commands (Edit, Delete, Save, Cancel)
- Custom command buttons
- Role-based and status-based commands
- Command click events
- Conditional command visibility
- CSS styling and icons
Localization & Internationalization
📄 Read: references/localization.md
- Multi-language support (60+ languages)
- Locale setup and culture configuration
- Number, date, and currency formatting
- RTL support (Arabic, Hebrew, Farsi)
- Custom localization and translation
- Language switcher implementation
Responsive Design & Mobile
📄 Read: references/responsive-design.md
- Adaptive UI for mobile/tablet/desktop
- Responsive media queries and breakpoints
- Column visibility hiding rules
- Touch interactions (swipe, long-press)
- Mobile optimization strategies
- Device-specific styling patterns
Programmatic Control
📄 Read: references/programmatic-api.md
- The full programmatic method catalog
- Event prop reference
- Dynamic column control
setProperties()examples, export hooks, and cross-feature
Event Communication
📄 Read: references/events-catalog.md
- Wire
actionBeginto cancel or mutate before an action (args.cancel = true,args.data.field = value) - Wire
actionCompleteto react after (API call, toast, refresh, toolbar restore) - Must wire
actionFailurefor error handling - Use
args.requestTypeto identify the action, see the requestType table in the events catalog
Advanced Tutorials & Real-World Patterns
📄 Read: references/advanced-tutorials.md
- Real-time data updates (WebSocket, SignalR)
- Master-detail with filtering
- Complex calculations and running totals
- Advanced filtering with complex predicates
- Custom themes and theme switching
- Performance monitoring techniques
API Reference - Properties
📄 Read: references/grid-properties.md
- Properties overview
- Core data properties
- Property organization by category
- Configuration reference
API Reference - Methods
📄 Read: references/grid-methods.md
- Methods overview
- Data manipulation methods
- Method parameters and signatures
- Return types and async handling
API Reference - Events
📄 Read: references/grid-events.md
- Events overview
- Data events
- Event lifecycle and timing
- Event arguments and properties
Testing & Quality Assurance
📄 Read: references/test-case.md
- Unit tests
- Integration tests
- Testing strategies
- Test case examples
Quick Start Example
import { Component, OnInit } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
interface Employee {
EmployeeID: number;
FirstName: string;
LastName: string;
Title: string;
HireDate: Date;
ReportsTo: number;
Salary: number;
}
@Component({
selector: 'app-grid',
template: `
<ejs-grid [dataSource]="data" [allowPaging]="true"
[pageSettings]="{ pageSize: 12 }"
[allowSorting]="true"
[allowFiltering]="true">
<e-columns>
<e-column field="EmployeeID" headerText="ID" width="100"></e-column>
<e-column field="FirstName" headerText="First Name" width="120"></e-column>
<e-column field="LastName" headerText="Last Name" width="120"></e-column>
<e-column field="Title" headerText="Title" width="150"></e-column>
<e-column field="HireDate" headerText="Hire Date" type="date"
format="yMd" width="130"></e-column>
<e-column field="Salary" headerText="Salary" type="number"
format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class GridComponent implements OnInit {
data: Employee[] = [];
ngOnInit() {
this.loadEmployeeData();
}
loadEmployeeData() {
// Load your data here
this.data = [
{ EmployeeID: 1, FirstName: 'Nancy', LastName: 'Davolio', Title: 'Sales Representative',
HireDate: new Date(1992, 4, 1), ReportsTo: 2, Salary: 60000 },
{ EmployeeID: 2, FirstName: 'Andrew', LastName: 'Fuller', Title: 'Vice President Sales',
HireDate: new Date(1992, 8, 14), ReportsTo: null, Salary: 97000 },
// ... more data
];
}
}Common Patterns
Pattern 1: Data Grid with Sorting, Filtering, and Paging
Combine sorting, filtering, and paging for a complete data exploration experience:
<ejs-grid [dataSource]="data"
[allowSorting]="true"
[allowFiltering]="true"
[allowPaging]="true"
[pageSettings]="{ pageSize: 20 }">
<e-columns>
<e-column field="EmployeeID" headerText="ID" width="100"></e-column>
<e-column field="FirstName" headerText="First Name" width="120"></e-column>
<!-- more columns -->
</e-columns>
</ejs-grid>Pattern 2: Inline Editing with Validation
Enable inline editing with form validation:
<ejs-grid [dataSource]="data"
[editSettings]="{ allowEditing: true, allowAdding: true, mode: 'Normal' }"
[toolbar]="['Add', 'Edit', 'Delete', 'Update', 'Cancel']">
<e-columns>
<e-column field="EmployeeID" headerText="ID" [isPrimaryKey]="true"
width="100"></e-column>
<e-column field="FirstName" headerText="First Name"
[validationRules]="{ required: true }" width="120"></e-column>
<!-- more columns -->
</e-columns>
</ejs-grid>Pattern 3: Exporting to PDF and Excel
Enable both PDF and Excel exports with toolbar:
<ejs-grid [dataSource]="data"
[toolbar]="['PdfExport', 'ExcelExport']"
[allowPdfExport]='true'
[allowExcelExport]='true'
(toolbarClick)='toolbarClick($event)'>
<e-columns>
<!-- your columns -->
</e-columns>
</ejs-grid>Pattern 4: Virtual Scrolling for Large Datasets
Optimize performance with virtual scrolling:
<ejs-grid [dataSource]="largeDataset"
[enableVirtualization]="true"
[pageSettings]="{ pageSize: 50 }">
<e-columns>
<!-- your columns -->
</e-columns>
</ejs-grid>Key Props & Configuration
| Property | Type | Purpose | Common Values |
|---|---|---|---|
dataSource | Array/DataManager | Grid data | Employee[], RemoteDataBinding |
allowSorting | boolean | Enable sorting | true, false |
allowFiltering | boolean | Enable filtering | true, false |
allowPaging | boolean | Enable pagination | true, false |
allowGrouping | boolean | Enable grouping | true, false |
editSettings | object | Edit configuration | { mode: 'Inline' } |
pageSettings | object | Paging options | { pageSize: 12 } |
enableVirtualization | boolean | Virtual scrolling | true, false |
allowExcelExport | boolean | Excel export | true, false |
allowPdfExport | boolean | PDF export | true, false |
toolbar | array | Toolbar items | ['Add', 'Edit', 'Delete'] |
columns | array | Column definitions | [{ field, headerText }] |
height | string/number | Grid height | '400px', 'auto' |
Common Use Cases
Scenario 1: Employee Directory Implement a searchable, sortable, filterable employee list with details on demand. → Combine: getting-started + data-binding + columns + selection + hierarchy-grid
Scenario 2: Data Entry Form Build a grid for adding and editing records with validation. → Combine: editing + validation + toolbar + filtering
Scenario 3: Sales Report Dashboard Create a highly customized grid with grouping, aggregates, and PDF export. → Combine: grouping + aggregates + pdf-export + style-and-appearance
Scenario 4: Real-time Data Monitor Display streaming data with virtual scrolling and state persistence. → Combine: scrolling + state-management + data-binding + adaptive
Scenario 5: Multi-level Organization Chart Show hierarchical org structure with detail rows for each level. → Combine: hierarchy-grid + row templates + cell styling
Accessibility (WCAG 2.2 & Section 508) in Angular Grid
Table of Contents
- When to Use This Skill
- Overview
- Keyboard Navigation
- WCAG 2.2 Compliance
- WAI-ARIA Implementation
- Screen Reader Support
- Color Contrast
- Focus Management
- Accessibility Testing
When to Use This Skill
Use this skill when you need to:
- Ensure WCAG 2.2 compliance — Build grids that meet Web Content Accessibility Guidelines Level AA standards
- Support keyboard navigation — Enable users to navigate grids without a mouse
- Screen reader support — Ensure screen readers can interpret and announce grid content properly
- Section 508 compliance — Meet U.S. federal accessibility requirements
- Color contrast — Verify that text and UI elements meet contrast ratio requirements
- Focus management — Implement proper focus handling and visual indicators
- WAI-ARIA implementation — Use ARIA attributes correctly for rich internet applications
- Accessibility testing — Test and validate grids for compliance with accessibility standards
- Inclusive design — Support users with disabilities including visual, motor, and cognitive impairments
Overview
Syncfusion Angular Grid is built with accessibility as a core principle, supporting:
- WCAG 2.2 (Web Content Accessibility Guidelines Level AA)
- Section 508 (U.S. accessibility standards)
- WAI-ARIA 1.2 (Web Accessibility Initiative - Accessible Rich Internet Applications)
- Keyboard-only navigation
- Screen reader compatibility
---
Keyboard Navigation
Grid Navigation Keys
| Key | Action |
|---|---|
| Tab | Move to next cell/control |
| Shift + Tab | Move to previous cell/control |
| Arrow Keys | Navigate within rows/columns |
| Ctrl + Home | Go to first cell |
| Ctrl + End | Go to last cell |
| Page Up | Previous page (paging enabled) |
| Page Down | Next page (paging enabled) |
| Space | Select row/check checkbox |
| Enter | Edit cell / Confirm action |
| Escape | Cancel edit / Close dialog |
| Ctrl + A | Select all |
| Ctrl + C | Copy (with clipboard enabled) |
| Ctrl + X | Cut (with clipboard enabled) |
| Ctrl + V | Paste (with clipboard enabled) |
Enable Keyboard Navigation
import { Component } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-grid',
template: `
<ejs-grid #grid
[dataSource]="data"
[allowKeyboard]="true"
[allowPaging]="true">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerID" headerText="Customer" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class AppGridComponent {
data = [];
constructor() {
// allowKeyboard=true is enabled by default
}
}Tab Index Order
Maintain logical tab order:
<ejs-grid [dataSource]="data" tabIndex="1">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>Keyboard Event Handling
import { Component, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-grid',
template: `
<div (keydown)="handleKeyDown($event)">
<ejs-grid #grid [dataSource]="data">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
</div>
`
})
export class AppComponent {
@ViewChild('grid') gridInstance: GridComponent;
data = [];
handleKeyDown(event: KeyboardEvent) {
if (event.key === 'F2') {
// Custom action on F2
const selectedRows = this.gridInstance.getSelectedRows();
if (selectedRows.length > 0) {
this.gridInstance.startEdit(selectedRows[0]);
}
}
}
}---
WCAG 2.2 Compliance
Perceivable
Information must be presentable to users:
// ✅ Provide text alternatives for images
<e-column field="Photo" headerText="Employee Photo">
<ng-template #template let-data>
<img [src]="data.Photo"
[alt]="'Photo of ' + data.FirstName + ' ' + data.LastName"
style="width: 32px; height: 32px;"</e-column>
</ng-template>
</e-column>
// ✅ Use semantic HTML
<ejs-grid [dataSource]="data" ariaLabel="Employee data grid">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
// ✅ Sufficient contrast ratios (4.5:1 for normal text)
<div [style]="{'color': '#000000', 'background-color': '#FFFFFF'}">
High contrast text
</div>Operable
Users must be able to navigate and interact:
// ✅ Keyboard accessible
<ejs-grid [dataSource]="data" [allowKeyboard]="true" [allowSelection]="true">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
// ✅ Provide skip links
import { Component } from '@angular/core';
@Component({
selector: 'app-grid',
template: `
<a href="#main-grid" class="skip-link">Skip to grid</a>
<ejs-grid id="main-grid" [dataSource]="data">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
`
})
export class AppComponent {
data = [];
}Understandable
Content must be clear and predictable:
// ✅ Use clear labels
<e-column field="OrderDate" headerText="Order Date" type="date" format="MM/dd/yyyy"></e-column>
// ✅ Predictable behavior
// ✅ Predictable behavior
(actionBegin)="handleActionBegin($event)"
// ✅ Clear instructions
<label for="grid-filter">Filter by customer:</label>
<ejs-grid id="grid-filter" [dataSource]="data" [allowFiltering]="true">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>Robust
Content must work with assistive technologies:
// ✅ Valid HTML
<ejs-grid
[dataSource]="data"
role="region"
aria-label="Sales orders table"
>
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
// ✅ Proper attribute usage
<e-column
field="Verified"
headerText="Verified"
type="boolean"
[displayAsCheckBox]="true"
[attr.aria-label]="'Is verified'"
></e-column>---
WAI-ARIA Implementation
ARIA Attributes
Syncfusion Grid automatically adds ARIA attributes:
// Automatically added by Grid:
// role='grid'
// role='row' for each row
// role='gridcell' for each cell
// role='button' for headers (clickable)
// aria-selected='true/false'
// aria-sort='ascending/descending/none'
// aria-expanded='true/false' (for detail rows)
// aria-disabled='true/false'
// aria-readonly='true/false'
<ejs-grid
[dataSource]="data"
[allowSorting]="true"
[detailTemplate]="detailTemplate"
>
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>Custom ARIA Labels
<ejs-grid
[dataSource]="data"
ariaLabel="Employee database"
>
<ng-template #rowTemplate let-data>
<tr [attr.aria-label]="'Employee ' + data.FirstName + ' ' + data.LastName">
<td>{{data.FirstName}}</td>
<td>{{data.LastName}}</td>
</tr>
</ng-template>
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>ARIA Live Regions
Announce dynamic content changes:
import { Component, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-grid',
template: `
<div>
<div role="status" aria-live="polite" aria-atomic="true" class="sr-only">
{{announcement}}
</div>
<ejs-grid #grid [dataSource]="data" (recordDoubleClick)="handleRowAction('Row opened')">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
</div>
`
})
export class AppAriaGridComponent {
handleRowAction(action: string) {
this.announcement = action + ' performed';
setTimeout(() => this.announcement = '', 1000);
}
}---
Screen Reader Support
Test with NVDA/JAWS
1. Install NVDA (free) or JAWS 2. Enable screen reader 3. Tab through grid 4. Verify announcements
Announce Selection
import { Component, ViewChild, OnInit } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-announce-selection',
template: `<ejs-grid #grid [dataSource]="data"
[selectionSettings]="{ type: 'Multiple', mode: 'Row' }"
(rowSelected)="onRowSelected($event)">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>`
})
export class GridAnnounceSelectionComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
data: any[] = [];
ngOnInit() {
this.loadData();
}
loadData() {
// Load grid data
}
onRowSelected(args: any) {
const announcement = 'Row ' + args.data.OrderID + ' selected';
this.announceToScreenReader(announcement);
}
annonounceToScreenReader(message: string) {
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => announcement.remove(), 1000);
}Describe Complex Cells
// Add descriptions for complex content
<e-column field="Status" headerText="Status">
<ng-template #template let-data>
<div>
<span [style]="{
'background-color': data.Status === 'Active' ? 'green' : 'red',
'color': 'white',
'padding': '4px 8px',
'border-radius': '4px'
}"
[attr.aria-label]="'Status: ' + data.Status">
{{data.Status}}
</span>
</div>
</ng-template>
</e-column>Header Descriptions
<e-column field="Freight" headerText="Freight Cost">
<ng-template #template>
<div>
<span>Freight Cost (USD)</span>
<span class="sr-only">Shipping cost in US dollars</span>
</div>
</ng-template>
</e-column>---
Color Contrast
WCAG AA Requirements
- Normal text: 4.5:1 ratio
- Large text (18+ or 14+ bold): 3:1 ratio
Contrast Check
// ✅ Good contrast (Black on White = 21:1)
<div [style]="{ 'color': '#000000', 'background-color': '#FFFFFF' }">
Text with excellent contrast
</div>
// ✅ Good for large text (3:1)
<div [style]="{ 'color': '#0066CC', 'background-color': '#FFFFFF', 'font-size': '18px', 'font-weight': 'bold' }">
Large blue text
</div>
// ❌ Bad contrast (Gray on White = 1.5:1)
<div [style]="{ 'color': '#CCCCCC', 'background-color': '#FFFFFF' }">
Poor contrast text (fail)
</div>Apply to Grid
// Custom theme with good contrast
const gridStyles = `
.e-grid .e-gridcontent td {
color: #000000; /* High contrast */
background-color: #FFFFFF;
}
.e-grid .e-headercell {
color: #FFFFFF;
background-color: #0066CC; /* 4.5:1 ratio */
}
.e-grid .e-selectionbackground {
background-color: #0066CC;
color: #FFFFFF;
}
`;
<ejs-grid [dataSource]="data">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>---
Focus Management
Visible Focus Indicator
const focusStyles = `
.e-grid .e-gridcontent td:focus,
.e-grid .e-headercell:focus {
outline: 3px solid #4A90E2;
outline-offset: 2px;
}
`;
<ejs-grid [dataSource]="data">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>Restore Focus on Dialog Close
import { Component, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-grid',
template: `
<ejs-grid #grid [dataSource]="data"
[editSettings]="{ mode: 'Dialog', allowEditing: true }"
(actionBegin)="handleActionBegin($event)"
(actionComplete)="handleActionComplete($event)">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
`
})
export class AppFocusGridComponent {
@ViewChild('grid') gridInstance: GridComponent;
data = [];
previousFocus: any;
handleActionBegin(args: any) {
if (args.requestType === 'beginEdit') {
this.previousFocus = document.activeElement;
}
}
handleActionComplete(args: any) {
if (args.requestType === 'save') {
if (this.previousFocus) {
(this.previousFocus as any).focus();
}
}
}
}Logical Tab Order
<ejs-grid [dataSource]="data" [editSettings]="{ mode: 'Dialog' }">
<e-columns>
<e-column field="FirstName" headerText="First Name" width="100" [tabIndex]="0"></e-column>
<e-column field="LastName" headerText="Last Name" width="100" [tabIndex]="1"></e-column>
</e-columns>
</ejs-grid>---
Accessibility Testing
Automated Testing with axe DevTools
import { TestBed, ComponentFixture } from '@angular/core/testing';
test('grid should be accessible', async () => {
const fixture = TestBed.createComponent(GridComponent);
const component = fixture.componentInstance;
fixture.detectChanges();
const container = fixture.nativeElement;
// Use accessibility testing library (e.g., axe-core)
// const results = await axe(container);
// Verify no accessibility violations
});Manual Testing Checklist
- [ ] All features keyboard accessible
- [ ] Tab order is logical
- [ ] Focus indicators visible
- [ ] No keyboard traps
- [ ] Color not only differentiator
- [ ] Contrast ratios met (4.5:1)
- [ ] Headings properly structured
- [ ] Labels associated with inputs
- [ ] Form validation messages clear
- [ ] Dynamic content announced
- [ ] Images have alt text
- [ ] Links have descriptive text
- [ ] No auto-playing audio/video
- [ ] Resizable text works
- [ ] Works with screen readers
- [ ] Responsive on mobile with zoom
Browser Extensions for Testing
- axe DevTools: Accessibility checker
- WAVE: Web accessibility tool
- Lighthouse: Built-in Chrome audit
- NVDA: Free screen reader (Windows)
- JAWS: Professional screen reader
Test Report Template
Grid Accessibility Audit - [Date]
✅ Keyboard Navigation
- All functions accessible
- Logical tab order
- No keyboard traps
✅ WCAG 2.2 Compliance
- Level A: PASSED
- Level AA: PASSED
✅ Screen Reader Support
- NVDA: PASSED
- JAWS: PASSED
✅ Color & Contrast
- Text: 4.5:1
- UI Components: 3:1
Notes:
- [Any issues found]Adaptive
Table of Contents
When to Use This Skill
Use this skill when you need to:
- Support mobile devices — Adapt grid layout for smaller screens and touch interfaces
- Tablet compatibility — Optimize grid display for tablet devices
- Responsive design — Automatically adjust columns and visibility based on screen size
- Adaptive UI — Enable the grid to respond dynamically to viewport changes
- Minimal width columns — Configure columns to hide or adjust when space is limited
- Touch-friendly interaction — Support tap-based navigation and controls on mobile devices
- Responsive navigation — Ensure grid controls and toolbars work on various screen sizes
Overview
Adaptive mode provides responsive grid layout for mobile and tablet devices. Grid automatically adjusts column visibility and behavior based on screen size.
Responsive Grid Layout
Enable adaptive/responsive mode:
import { Component } from '@angular/core';
@Component({
selector: 'app-adaptive-grid',
template: `
<p style="padding: 10px; color: #666;">
Resize browser or view on mobile to see adaptive behavior
</p>
<div class="e-bigger">
<ejs-grid [dataSource]="data"
[enableAdaptiveUI]="true"
[allowPaging]="true"
height="400">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100" minWidth="50"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150" minWidth="80"></e-column>
<e-column field="ShipCity" headerText="Ship City" width="150" minWidth="80"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120" minWidth="60"></e-column>
</e-columns>
</ejs-grid>
</div>
`
})
export class AdaptiveGridComponent {
data = [
{ OrderID: 10248, CustomerName: 'VINET', ShipCity: 'Reims', Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', ShipCity: 'München', Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', ShipCity: 'Rio de Janeiro', Freight: 65.83 },
{ OrderID: 10251, CustomerName: 'VICTE', ShipCity: 'Rio de Janeiro', Freight: 41.34 }
];
}Data Adaptors in Angular Grid
Table of Contents
- When to Use This Skill
- Overview
- URL Adaptor
- ODataV4 Adaptor
- WebAPI Adaptor
- GraphQL Adaptor
- Custom Adaptor
- RemoteSave Adaptor
- Adaptor Comparison
- Error Handling
When to Use This Skill
Use this skill when you need to:
- Connect to REST APIs — Configure grid to fetch data from RESTful backend services
- Integrate with OData services — Use ODataV4 adaptor for OData-compliant APIs
- WebAPI integration — Connect to .NET WebAPI endpoints with proper parameter binding
- GraphQL support — Integrate grid with GraphQL backend services
- Custom backend protocols — Build custom adaptors for proprietary or specialized backend systems
- Server-side operations — Offload filtering, sorting, grouping, and paging to the server
- Error handling — Implement robust error handling for backend communication failures
- Data transformation — Transform server responses before binding to grid
- Cross-domain requests — Configure CORS and cross-domain data fetching
Overview
Data adaptors provide an interface between the Grid component and various backend services. They handle data fetching, filtering, sorting, grouping, and CRUD operations on the server side.
Adaptors are configured through the DataManager component using the adaptor property.
URL Adaptor
Simplest adaptor for REST APIs with standard HTTP GET/POST requests.
Setup
import { Component } from '@angular/core';
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-url-adaptor-grid',
template: `<ejs-grid [dataSource]="dataManager">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>`
})
export class UrlAdaptorGridComponent {
public dataManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor(),
crossDomain: true
});
}Server Expectations
The server expects:
- GET request for reading data
- Query parameters:
$skip,$top,$orderby,$filter
Example URL:
GET url?$skip=0&$top=12&$orderby=OrderID%20desc&$filter=Freight%20gt%2050Server Response Format
{
"d": [
{ "OrderID": 10248, "CustomerID": "VINET", "Freight": 32.38 },
{ "OrderID": 10249, "CustomerID": "TOMSP", "Freight": 11.61 }
],
"__count": 830
}Or with array response:
[
{ "OrderID": 10248, "CustomerID": "VINET", "Freight": 32.38 },
{ "OrderID": 10249, "CustomerID": "TOMSP", "Freight": 11.61 }
]Example Implementation
import { Component } from '@angular/core';
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-grid',
template: `
<ejs-grid [dataSource]="dataManager"
[allowPaging]="true"
[allowSorting]="true"
[allowFiltering]="true">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerID" headerText="Customer" width="120"></e-column>
<e-column field="Freight" headerText="Freight" width="100" format="C2"></e-column>
</e-columns>
</ejs-grid>
`
})
export class UrlAdaptorGridComponent {
public dataManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor(),
crossDomain: true
});
}---
ODataV4 Adaptor
Specialized for OData v4 protocol. Provides advanced filtering, sorting, and server-side operations.
Setup
import { Component } from '@angular/core';
import { DataManager, ODataV4Adaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-grid',
template: `<ejs-grid [dataSource]="dataManager">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>`
})
export class ODataV4GridComponent {
public dataManager = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor(),
pageSize: 12
});
}Benefits
- Advanced Filtering: Complex filter expressions
- Selective Loading: Only required columns via $select
- Server-Side Aggregates: Count, sum, average on server
- Relationship Navigation: Access related entities via $expand
Query Parameters
$select - Select specific columns
$filter - Complex filter expressions
$orderby - Sort by one or more fields
$skip - Skip N records (pagination)
$top - Return N records
$count - Include total count
$expand - Include related dataExample Queries
// Basic with paging
/Orders?$skip=0&$top=12&$count=true
// With filtering
/Orders?$filter=Freight gt 50 and ShipCity eq 'London'
// With sorting
/Orders?$orderby=OrderDate desc,CustomerID asc
// Selective columns
/Orders?$select=OrderID,CustomerID,Freight
// With relationships
/Orders?$expand=Customer,Employee
// Complex filter
/Orders?$filter=contains(tolower(CustomerID),'a') and Freight lt 100Complex OData Filters
import { Predicate } from '@syncfusion/ej2-data';
const predicate = new Predicate('Freight', 'greaterThan', 50);
predicate = predicate.and('ShipCity', 'equal', 'London');
predicate = predicate.or('CustomerID', 'startsWith', 'A');
const gridInstance = this.gridInstance;
gridInstance.query = new Query().where(predicate);
gridInstance.refresh();Example with Expand
const dataManager = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor()
});
export class ODataGridComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
data = dataManager;
ngOnInit() {
// Data is automatically managed by DataManager
}
}
// In template:
<ejs-grid [dataSource]="data" [allowPaging]="true" [allowSorting]="true" [allowFiltering]="true">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="Customer.CompanyName" headerText="Company" width="150"></e-column>
<e-column field="Employee.FirstName" headerText="Employee" width="120"></e-column>
<e-column field="Freight" headerText="Freight" width="100" format="C2"></e-column>
</e-columns>
<e-inject [services]="[Page, Sort, Filter]"></e-inject>
</ejs-grid>---
WebAPI Adaptor
For ASP.NET Web API services using RESTful conventions.
Setup
import { Component } from '@angular/core';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-grid',
template: `<ejs-grid [dataSource]="dataManager">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>`
})
export class WebApiAdaptorGridComponent {
crossDomain: true
});
}ASP.NET Web API Controller
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet]
public IActionResult GetOrders([FromQuery] DataManagerRequest dm)
{
var orders = GetAllOrders(); // Your data source
if (dm.Search != null && dm.Search.Count > 0)
{
orders = orders.Where(o => o.CustomerID.Contains(dm.Search[0].Key)).ToList();
}
if (dm.Sorted != null && dm.Sorted.Count > 0)
{
foreach (var sort in dm.Sorted)
{
if (sort.Direction == "Ascending")
orders = orders.OrderBy(x => x.GetType().GetProperty(sort.Name)?.GetValue(x)).ToList();
else
orders = orders.OrderByDescending(x => x.GetType().GetProperty(sort.Name)?.GetValue(x)).ToList();
}
}
var count = orders.Count();
if (dm.Skip > 0)
orders = orders.Skip(dm.Skip).ToList();
if (dm.Take > 0)
orders = orders.Take(dm.Take).ToList();
return Ok(new { result = orders, count = count });
}
[HttpPost]
public IActionResult Create([FromBody] Order value)
{
// Insert logic
return Ok(value);
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] Order value)
{
// Update logic
return Ok(value);
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
// Delete logic
return Ok(id);
}
}React Grid With CRUD
import { Component } from '@angular/core';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-grid',
template: `
<ejs-grid [dataSource]="dataManager"
[editSettings]="{ allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Dialog' }"
toolbar="['Add', 'Edit', 'Delete', 'Update', 'Cancel']">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
`
})
export class WebApiCrudGridComponent {
insertUrl: 'url/insert',
updateUrl: 'url/update',
removeUrl: 'url/remove',
batchUrl: 'url/batch'
});
}---
GraphQL Adaptor
For GraphQL endpoints returning complex nested queries.
Setup
import { DataManager, GraphQLAdaptor } from '@syncfusion/ej2-data';
const data = new DataManager({
url: 'url',
adaptor: new GraphQLAdaptor(),
query: `query {
orders(skip: 0, take: 12) {
items {
orderId
customerId
freight
}
totalCount
}
}`
});
<ejs-grid [dataSource]="data">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>GraphQL Query with Variables
const data = new DataManager({
url: 'url',
adaptor: new GraphQLAdaptor(),
query: `query getOrders($skip: Int!, $take: Int!, $filter: String) {
orders(skip: $skip, take: $take, filter: $filter) {
items {
orderId
customerId
orderDate
freight
}
totalCount
}
}`
});GraphQL API Example (Node.js)
const express = require('express');
const { graphql, buildSchema } = require('graphql');
const schema = buildSchema(`
type Query {
orders(skip: Int, take: Int, filter: String): OrderResult
}
type Order {
orderId: Int
customerId: String
orderDate: String
freight: Float
}
type OrderResult {
items: [Order]
totalCount: Int
}
`);
const root = {
orders: ({ skip = 0, take = 12, filter }) => {
let orders = getAllOrders();
if (filter) {
orders = orders.filter(o => o.customerId.includes(filter));
}
return {
items: orders.slice(skip, skip + take),
totalCount: orders.length
};
}
};
app.post('/graphql', express.json(), async (req, res) => {
const result = await graphql(schema, req.body.query, root);
res.json(result);
});---
Custom Adaptor
For non-standard backend implementations.
Creating a Custom Adaptor
import { DataManager, Adaptor, Query } from '@syncfusion/ej2-data';
class CustomAdaptor extends Adaptor {
processQuery(dm, query, hierarchyIndex) {
// Custom query processing
const req = this.baseUrl;
const filter = query.params.filter || '';
const sort = query.params.sort || '';
const page = query.params.pageIndex || 1;
const pageSize = query.params.pageSize || 12;
return {
type: 'GET',
url: `${req}?filter=${filter}&sort=${sort}&page=${page}&pageSize=${pageSize}`
};
}
processResponse(data, dm, query, xhr, request, key) {
// Process response to expected format
if (data.result) {
return { result: data.result, count: data.totalCount };
}
return data;
}
insert(dm, value, tableName, key) {
// Custom insert logic
return {
type: 'POST',
url: dm.insertUrl || dm.baseUrl,
data: JSON.stringify(value),
contentType: 'application/json'
};
}
update(dm, keyField, value, tableName, key) {
// Custom update logic
return {
type: 'PUT',
url: `${dm.updateUrl || dm.baseUrl}/${value[keyField]}`,
data: JSON.stringify(value),
contentType: 'application/json'
};
}
remove(dm, keyField, value, tableName, key) {
// Custom delete logic
return {
type: 'DELETE',
url: `${dm.removeUrl || dm.baseUrl}/${value[keyField]}`
};
}
}
const data = new DataManager({
url: 'url',
adaptor: new CustomAdaptor()
});Custom Adaptor with Special Headers
class AuthenticatedAdaptor extends Adaptor {
processRequest(dm, request, state) {
request.headers = {
...request.headers,
'Authorization': `send_token`,
'X-Custom-Header': 'CustomValue'
};
return request;
}
processResponse(data, dm, query, xhr, request, key) {
if (xhr.status === 401) {
// Handle unauthorized
redirectToLogin();
}
return data;
}
}---
RemoteSave Adaptor
Specialized for batch CRUD operations (multiple insert/update/delete in single request).
Setup
import { DataManager, RemoteSaveAdaptor } from '@syncfusion/ej2-data';
import { GridComponent, Inject, Edit, Toolbar } from '@syncfusion/ej2-angular-grids';
const data = new DataManager({
url: 'url',
adaptor: new RemoteSaveAdaptor(),
batchUrl: 'url/batch'
});
export class BatchGridComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
data = dataManager;
editSettings: any = {
allowEditing: true,
allowAdding: true,
allowDeleting: true,
mode: 'Batch'
};
toolbar: string[] = ['Add', 'Edit', 'Delete', 'Update', 'Cancel'];
}
<ejs-grid [dataSource]="data" [editSettings]="editSettings" [toolbar]="toolbar">
<e-columns>
<!-- columns -->
</e-columns>
<e-inject [services]="[Edit, Toolbar]"></e-inject>
</ejs-grid>Batch Request Format
{
"changed": [
{ "OrderID": 10248, "CustomerID": "VINET_UPDATED" }
],
"added": [
{ "CustomerID": "NEWCUST", "Freight": 25.5 }
],
"deleted": [
{ "OrderID": 10249 }
]
}ASP.NET Backend
[HttpPost("batch")]
public IActionResult UpdateBatch([FromBody] BatchRequest request)
{
if (request.Changed != null)
foreach (var order in request.Changed)
UpdateOrder(order);
if (request.Added != null)
foreach (var order in request.Added)
InsertOrder(order);
if (request.Deleted != null)
foreach (var order in request.Deleted)
DeleteOrder(order.OrderID);
return Ok();
}
public class BatchRequest
{
public List<Order> Changed { get; set; }
public List<Order> Added { get; set; }
public List<Order> Deleted { get; set; }
}---
Adaptor Comparison
| Feature | URL | OData | WebAPI | GraphQL | Custom |
|---|---|---|---|---|---|
| Easy Setup | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⚠️ Complex |
| Filtering | ⭐⭐⭐ | ⭐⭐⭐ | Good | Complex | Custom |
| Sorting | ⭐⭐⭐ | ⭐⭐⭐ | Good | Custom | Custom |
| Pagination | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | Custom | Custom |
| Aggregates | ⭐⭐⭐ | ⭐⭐⭐ | ⚠️ | Custom | Custom |
| Relationships | ❌ | ⭐⭐⭐ | Basic | ⭐⭐⭐ | Custom |
| CRUD | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | Custom | Custom |
| Performance | Good | Good | Good | Variable | Variable |
---
Error Handling
Global Error Handler
const data = new DataManager({
url: 'url',
adaptor: new UrlAdaptor()
});
export class ErrorHandlingGridComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
data = dataManager;
onActionFailure(args: any) {
console.error('Grid Error:', args);
if (args.error?.status === 401) {
this.redirectToLogin();
} else if (args.error?.status === 500) {
this.showErrorNotification('Server error occurred');
} else {
this.showErrorNotification('An error occurred');
}
}
redirectToLogin() {
// Implement redirect
}
showErrorNotification(message: string) {
// Implement notification
}
}Request/Response Interceptor
class InterceptorAdaptor extends Adaptor {
beforeSend(request: any) {
console.log('Request:', request);
return request;
}
processResponse(data: any, dm: any, query: any, xhr: any, request: any, key: string) {
console.log('Response:', data);
if (data.error) {
throw new Error(data.error.message);
}
return data;
}
}Advanced Tutorials & Patterns
Table of Contents
- When to Use This Skill
- Overview
- Real-Time Data Updates
- Master-Detail with Filtering
- Complex Calculations
- Advanced Filtering
- Custom Themes
- Performance Monitoring
When to Use This Skill
Use this skill when you need to:
- Real-time data updates — Implement auto-refresh or WebSocket-based live data updates
- Master-detail scenarios — Create hierarchical grids with parent-child data relationships
- Master-detail filtering — Filter detail grid based on master row selection
- Complex calculations — Implement computed columns with custom formulas
- Advanced filtering — Build sophisticated multi-criteria filtering UIs
- Custom themes — Develop branded or custom-styled grid appearances
- Performance optimization — Monitor and optimize grid performance for large datasets
- Production patterns — Apply real-world best practices and proven patterns
- Complex workflows — Implement multi-step data processing and validation workflows
Overview
This guide covers advanced patterns, real-world scenarios, and best practices for production grids.
---
Real-Time Data Updates
Auto-Refresh Grid Data
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-auto-refresh-grid',
template: `
<div>
<label>Refresh every:
<select [(ngModel)]="refreshInterval" (change)="onRefreshIntervalChange()">
<option [value]="1000">1 second</option>
<option [value]="5000">5 seconds</option>
<option [value]="10000">10 seconds</option>
<option [value]="30000">30 seconds</option>
</select>
</label>
<ejs-grid #grid [dataSource]="data" [allowPaging]="true">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
</div>
`
})
export class AutoRefreshGridComponent implements OnInit, OnDestroy {
@ViewChild('grid') gridInstance: GridComponent;
data: any[] = [];
refreshInterval = 5000;
private intervalId: any;
ngOnInit() {
this.startAutoRefresh();
}
ngOnDestroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
startAutoRefresh() {
this.intervalId = setInterval(async () => {
try {
const response = await fetch('/api/orders');
this.data = await response.json();
} catch (error) {
console.error('Refresh error:', error);
}
}, this.refreshInterval);
}
onRefreshIntervalChange() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
this.startAutoRefresh();
}
}WebSocket Real-Time Updates
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-websocket-grid',
template: `
<ejs-grid #grid [dataSource]="data" [allowPaging]="true">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
`
})
export class WebSocketGridComponent implements OnInit, OnDestroy {
@ViewChild('grid') gridInstance: GridComponent;
data: any[] = [];
private ws: WebSocket;
ngOnInit() {
this.connectWebSocket();
}
ngOnDestroy() {
if (this.ws) {
this.ws.close();
}
}
connectWebSocket() {
this.ws = new WebSocket('url');
this.ws.onmessage = (event) => {
const newData = JSON.parse(event.data);
const index = this.data.findIndex(item => item.OrderID === newData.OrderID);
if (index >= 0) {
this.data[index] = newData;
} else {
this.data.push(newData);
}
this.gridInstance.refresh();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
}SignalR Real-Time Updates
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-signalr-grid',
template: `
<ejs-grid #grid [dataSource]="data" [allowPaging]="true">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
`
})
export class SignalRGridComponent implements OnInit, OnDestroy {
@ViewChild('grid') gridInstance: GridComponent;
data: any[] = [];
private connection: signalR.HubConnection;
ngOnInit() {
this.initializeSignalR();
}
ngOnDestroy() {
if (this.connection) {
this.connection.stop();
}
}
initializeSignalR() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl('/orderHub')
.withAutomaticReconnect()
.build();
this.connection.on('OrderUpdated', (updatedOrder) => {
const index = this.data.findIndex(o => o.OrderID === updatedOrder.OrderID);
if (index >= 0) {
this.data[index] = updatedOrder;
this.gridInstance.setCellValue(index, 'Freight', updatedOrder.Freight);
}
});
this.connection.on('OrderAdded', (newOrder) => {
this.gridInstance.addRecord(newOrder);
});
this.connection.start().catch(err => console.error('Connection error:', err));
}
}---
Master-Detail with Filtering
Filtered Detail Grid
import { Component, OnInit } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-master-detail-grid',
template: `
<ejs-grid [dataSource]="orders" (recordClick)="handleMasterRowClick($event)">
<ng-template #detailTemplate let-data>
<ejs-grid [dataSource]="getOrderDetails(data.OrderID)" [allowPaging]="true" [pageSettings]="{ pageSize: 5 }">
<e-columns>
<e-column field="ProductID" headerText="Product ID" width="100"></e-column>
<e-column field="ProductName" headerText="Product" width="150"></e-column>
<e-column field="Quantity" headerText="Quantity" width="100"></e-column>
<e-column field="UnitPrice" headerText="Unit Price" width="100" format="C2"></e-column>
</e-columns>
</ejs-grid>
</ng-template>
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerID" headerText="Customer" width="120"></e-column>
<e-column field="OrderDate" headerText="Order Date" type="date" format="yMd"></e-column>
<e-column field="Freight" headerText="Freight" width="100" format="C2"></e-column>
</e-columns>
</ejs-grid>
`
})
export class MasterDetailGridComponent implements OnInit {
orders: any[] = [];
selectedOrder: any;
ngOnInit() {
this.loadOrders();
}
loadOrders() {
// Load orders data
this.orders = [...]; // Load from service
}
getOrderDetails(orderId: number) {
// Fetch and return detail data for the order
return [];
}
handleMasterRowClick(args: any) {
this.selectedOrder = args.rowData;
}
}---
Complex Calculations
Calculated Columns with Aggregates
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-calculations-grid',
template: `
<ejs-grid [dataSource]="data">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="Freight" headerText="Freight" width="100" format="C2"></e-column>
<e-column field="Tax" headerText="Tax (10%)" width="100" format="C2"></e-column>
<e-column field="Total" headerText="Total" width="100" format="C2"></e-column>
</e-columns>
<e-aggregates>
<e-aggregate>
<e-columns>
<e-column field="Freight" type="Sum"></e-column>
<e-column field="Tax" type="Sum"></e-column>
<e-column field="Total" type="Sum"></e-column>
</e-columns>
</e-aggregate>
</e-aggregates>
</ejs-grid>
`
})
export class CalculationsGridComponent implements OnInit {
data: any[] = [];
ngOnInit() {
this.loadOrdersWithCalculations();
}
loadOrdersWithCalculations() {
const orders = [...]; // Load from service
this.data = orders.map(order => ({
...order,
Tax: order.Freight * 0.1,
Total: order.Freight + (order.Freight * 0.1)
}));
}
}Running Totals
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-running-total-grid',
template: `
<ejs-grid [dataSource]="enhancedData">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="Freight" headerText="Freight" width="100" format="C2"></e-column>
<e-column field="RunningTotal" headerText="Running Total" width="150" format="C2"></e-column>
</e-columns>
</ejs-grid>
`
})
export class RunningTotalGridComponent implements OnInit {
enhancedData: any[] = [];
private runningTotal = 0;
ngOnInit() {
this.calculateRunningTotals();
}
calculateRunningTotals() {
const orders = [...]; // Load from service
this.runningTotal = 0;
this.enhancedData = orders.map(order => {
this.runningTotal += order.Freight;
return {
...order,
RunningTotal: this.runningTotal
};
});
}
}---
Advanced Filtering
Multi-Column Complex Filter
import { Component, ViewChild, OnInit } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import { Predicate, Query } from '@syncfusion/ej2-data';
import { FilterService } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-complex-filter-grid',
template: `
<button (click)="applyAdvancedFilter()">Apply Complex Filter</button>
<ejs-grid #grid [dataSource]="orders" [allowFiltering]="true" [filterSettings]="{ type: 'Menu' }">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerID" headerText="Customer" width="120"></e-column>
<e-column field="Freight" headerText="Freight" width="100" format="C2"></e-column>
<e-column field="Status" headerText="Status" width="100"></e-column>
</e-columns>
</ejs-grid>
`,
providers: [FilterService]
})
export class ComplexFilterGridComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
orders: any[] = [];
ngOnInit() {
this.loadOrders();
}
loadOrders() {
this.orders = [...]; // Load from service
}
applyAdvancedFilter() {
const predicate = new Predicate('Freight', 'greaterThan', 50);
predicate.and('Status', 'equal', 'Active');
predicate.or('CustomerID', 'equal', 'VINET');
this.gridInstance.query = new Query().where(predicate);
this.gridInstance.refresh();
}
}Date Range Filtering
import { Component, ViewChild, OnInit } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import { Predicate, Query } from '@syncfusion/ej2-data';
import { FilterService } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-date-range-filter-grid',
template: `
<div>
<label>Start Date:
<input type="date" [(ngModel)]="startDateString" (change)="onStartDateChange()"</e-column>
</label>
<label>End Date:
<input type="date" [(ngModel)]="endDateString" (change)="onEndDateChange()"</e-column>
</label>
<button (click)="applyDateFilter()">Apply Date Filter</button>
</div>
<ejs-grid #grid [dataSource]="orders" [allowFiltering]="true">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="OrderDate" headerText="Order Date" type="date" format="yMd" width="120"></e-column>
<e-column field="Freight" headerText="Freight" width="100" format="C2"></e-column>
</e-columns>
</ejs-grid>
`,
providers: [FilterService]
})
export class DateRangeFilterGridComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
orders: any[] = [];
startDate: Date;
endDate: Date;
startDateString: string;
endDateString: string;
ngOnInit() {
this.loadOrders();
}
loadOrders() {
this.orders = [...]; // Load from service
}
onStartDateChange() {
this.startDate = new Date(this.startDateString);
}
onEndDateChange() {
this.endDate = new Date(this.endDateString);
}
applyDateFilter() {
if (this.startDate && this.endDate) {
const predicate = new Predicate('OrderDate', 'greaterthanorequal', this.startDate);
predicate.and('OrderDate', 'lessthanorequal', this.endDate);
this.gridInstance.query = new Query().where(predicate);
this.gridInstance.refresh();
}
}
}---
Custom Themes
Dynamic Theme Switching
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-theme-switcher-grid',
template: `
<select [(ngModel)]="theme" (change)="applyTheme()">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="blue">Blue</option>
</select>
<div [ngClass]="'themed-grid-' + theme">
<ejs-grid [dataSource]="data">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
</div>
`,
styles: [`
.themed-grid-light {
background-color: #ffffff;
color: #000000;
}
.themed-grid-light .e-headercell {
background-color: #f5f5f5;
color: #000000;
}
.themed-grid-dark {
background-color: #1e1e1e;
color: #ffffff;
}
.themed-grid-dark .e-headercell {
background-color: #333333;
color: #ffffff;
}
.themed-grid-blue {
background-color: #e3f2fd;
color: #1565c0;
}
.themed-grid-blue .e-headercell {
background-color: #1976d2;
color: #ffffff;
}
`]
})
export class ThemeSwitcherGridComponent implements OnInit {
data: any[] = [];
theme = 'light';
ngOnInit() {
this.loadData();
}
loadData() {
this.data = [...]; // Load from service
}
applyTheme() {
// Theme applied via ngClass binding
}
}---
Performance Monitoring
Measure Grid Performance
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-performance-monitor-grid',
template: `
<button (click)="measureInitialLoad()">Measure Load Time</button>
<button (click)="countDataFetches()">Show Network Metrics</button>
<ejs-grid #grid [dataSource]="data" (actionComplete)="onActionComplete($event)">
<e-columns>
<!-- columns -->
</e-columns>
</ejs-grid>
`
})
export class PerformanceGridComponent implements OnInit, OnDestroy {
@ViewChild('grid') gridInstance: GridComponent;
data: any[] = [];
private observer: PerformanceObserver;
ngOnInit() {
this.initializePerformanceMonitoring();
}
ngOnDestroy() {
if (this.observer) {
this.observer.disconnect();
}
}
initializePerformanceMonitoring() {
this.observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration}ms`);
}
});
this.observer.observe({ entryTypes: ['measure'] });
}
measureInitialLoad() {
performance.mark('grid-load-start');
// Grid renders here
performance.mark('grid-load-end');
performance.measure('grid-load', 'grid-load-start', 'grid-load-end');
}
countDataFetches() {
const navigationTiming = performance.getEntriesByType('navigation')[0];
console.log('Network timing:', {
dnsLookup: navigationTiming.domainLookupEnd - navigationTiming.domainLookupStart,
tcpConnect: navigationTiming.connectEnd - navigationTiming.connectStart,
ttfb: navigationTiming.responseStart - navigationTiming.requestStart,
download: navigationTiming.responseEnd - navigationTiming.responseStart
});
}
onActionComplete(args: any) {
console.log(`Action '${args.requestType}' completed at ${performance.now()}ms`);
}
}Aggregates
Table of Contents
When to Use This Skill
Use this skill when you need to:
- Display summary statistics — Show sum, average, count, min, max for numeric columns
- Financial reporting — Display totals, subtotals, and aggregate calculations for financial data
- Group summaries — Show aggregates at group level with expandable group rows
- Footer summaries — Display aggregate totals at the bottom of the grid
- Custom aggregation — Implement custom aggregate functions beyond built-in ones
- Multi-level aggregation — Combine aggregates across multiple grouping levels
- Analytics display — Present statistical summaries alongside detailed data
- Budget tracking — Calculate running totals, spend summaries, and variance calculations
Overview
Aggregates display summary information (sum, average, count, etc.) for numeric columns. Use them for financial data, statistics, and analytics.
Basic Aggregates
Display aggregate summary rows:
import { Component } from '@angular/core';
import { AggregateColumnsModel, AggregateRowsModel } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-aggregates-grid',
template: `
<ejs-grid [dataSource]="data" [allowPaging]="true">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Quantity" headerText="Quantity" type="number" width="100"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
<e-aggregates>
<e-aggregate>
<e-columns>
<e-column field="Quantity" type="Sum" footerTemplate="Total: ${Sum}"></e-column>
<e-column field="Freight" type="Sum" footerTemplate="Total: ${Sum}"></e-column>
</e-columns>
</e-aggregate>
</e-aggregates>
</ejs-grid>
`
})
export class AggregatesGridComponent {
data = [
{ OrderID: 10248, CustomerName: 'VINET', Quantity: 5, Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Quantity: 10, Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', Quantity: 8, Freight: 65.83 }
];
}Footer Aggregates
Display aggregate values in footer rows:
import { Component } from '@angular/core';
@Component({
selector: 'app-footer-aggregate-grid',
template: `
<ejs-grid [dataSource]="data" [allowPaging]="true">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Quantity" headerText="Quantity" type="number" width="100"></e-column>
<e-column field="UnitPrice" headerText="Unit Price" type="number" format="C2" width="120"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
<e-aggregates>
<e-aggregate>
<e-columns>
<e-column field="Quantity" type="Sum" footerTemplate="Sum: ${Sum}"></e-column>
<e-column field="Quantity" type="Average" footerTemplate="Avg: ${Average}"></e-column>
<e-column field="Freight" type="Sum" footerTemplate="Total: ${Sum}"></e-column>
</e-columns>
</e-aggregate>
</e-aggregates>
</ejs-grid>
`
})
export class FooterAggregateGridComponent {
data = [
{ OrderID: 10248, CustomerName: 'VINET', Quantity: 5, UnitPrice: 10, Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Quantity: 10, UnitPrice: 15, Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', Quantity: 8, UnitPrice: 12, Freight: 65.83 }
];
}Custom Aggregates
Create custom aggregate functions:
import { Component } from '@angular/core';
@Component({
selector: 'app-custom-aggregate-grid',
template: `
<ejs-grid [dataSource]="data" [allowPaging]="true">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Quantity" headerText="Quantity" type="number" width="100"></e-column>
<e-column field="UnitPrice" headerText="Unit Price" type="number" format="C2" width="120"></e-column>
</e-columns>
<e-aggregates>
<e-aggregate>
<e-columns>
<e-column field="Quantity" type="Custom"
footerTemplate="Median: ${Custom}" customAggregate="medianValue">
</e-column>
</e-columns>
</e-aggregate>
</e-aggregates>
</ejs-grid>
`
})
export class CustomAggregateGridComponent {
data = [
{ OrderID: 10248, CustomerName: 'VINET', Quantity: 5, UnitPrice: 10 },
{ OrderID: 10249, CustomerName: 'TOMSP', Quantity: 10, UnitPrice: 15 },
{ OrderID: 10250, CustomerName: 'HANAR', Quantity: 8, UnitPrice: 12 },
{ OrderID: 10251, CustomerName: 'VICTE', Quantity: 12, UnitPrice: 20 }
];
// Custom aggregate: Median value
medianValue(data: any[]): number {
const values = data
.map((item: any) => item.Quantity)
.sort((a, b) => a - b);
const mid = Math.floor(values.length / 2);
return values.length % 2 ? values[mid] : (values[mid - 1] + values[mid]) / 2;
}
}Group Aggregates
Display aggregates within grouped rows:
import { Component } from '@angular/core';
import { GroupSettingsModel } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-group-aggregate-grid',
template: `
<ejs-grid [dataSource]="data"
[allowGrouping]="true"
[groupSettings]="groupSettings">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="ShipCity" headerText="Ship City" width="150"></e-column>
<e-column field="Quantity" headerText="Quantity" type="number" width="100"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
<e-aggregates>
<e-aggregate>
<e-columns>
<e-column field="Quantity" type="Sum" groupFooterTemplate="Total: ${Sum}"></e-column>
<e-column field="Freight" type="Sum" groupFooterTemplate="Total: ${Sum}"></e-column>
</e-columns>
</e-aggregate>
</e-aggregates>
</ejs-grid>
`
})
export class GroupAggregateGridComponent {
groupSettings: GroupSettingsModel = {
columns: ['ShipCity']
};
data = [
{ OrderID: 10248, CustomerName: 'VINET', ShipCity: 'Reims', Quantity: 5, Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', ShipCity: 'München', Quantity: 10, Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', ShipCity: 'Rio de Janeiro', Quantity: 8, Freight: 65.83 },
{ OrderID: 10251, CustomerName: 'VICTE', ShipCity: 'Rio de Janeiro', Quantity: 12, Freight: 41.34 }
];
}Template prop determines where the aggregate appears in the group:
- groupFooterTemplate → rendered in the footer row of each group (below group rows)- groupCaptionTemplate → rendered in the caption row of each group (at the top of the group)- footerTemplate → rendered in the overall grid footer (not per group — do not use this for group summaries)Cell
Table of Contents
When to Use This Skill
Use this skill when you need to:
- Configure cell-level properties — Set individual cell widths, alignment, and behavior
- Implement cell editing — Enable inline editing of specific cells with validation
- Handle cell events — Respond to cell selection, editing, and content changes
- Style cells conditionally — Apply custom styling based on cell values or conditions
- Cell selection — Enable single or multiple cell selection with keyboard support
- Cell templates — Create custom cell content with HTML templates
- Cell validation — Validate data before saving cell changes
- Cell formatting — Format cell content (currency, dates, decimals) for display
Overview
Cells are individual data containers. Control cell editing, content, and appearance with properties and templates.
Cell Properties
Configure cell-level settings:
import { Component } from '@angular/core';
@Component({
selector: 'app-cell-grid',
template: `
<ejs-grid [dataSource]="data"
[allowSelection]="true"
[selectionSettings]="{ mode: 'Cell', type: 'Multiple' }">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100" [allowSorting]="false"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2"
width="120" [textAlign]="'Right'"></e-column>
<e-column field="OrderDate" headerText="Order Date" type="date"
format="yMd" width="130" [editType]="'DatePicker'"></e-column>
</e-columns>
</ejs-grid>
`
})
export class CellGridComponent {
data = [
{ OrderID: 10248, CustomerName: 'VINET', Freight: 32.38, OrderDate: new Date(1996, 6, 4) },
{ OrderID: 10249, CustomerName: 'TOMSP', Freight: 11.61, OrderDate: new Date(1996, 6, 5) }
];
}Cell Editing
Enable cell-level editing:
import { Component } from '@angular/core';
import { EditSettingsModel } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-cell-edit-grid',
template: `
<p style="padding: 10px; color: #666;">Click on cells to edit double-click to enable edit</p>
<ejs-grid [dataSource]="data"
[editSettings]="editSettings"
[allowSelection]="true"
[selectionSettings]="{ mode: 'Cell', type: 'Multiple' }">
<e-columns>
<e-column field="OrderID" headerText="Order ID" [isPrimaryKey]="true" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class CellEditGridComponent {
editSettings: EditSettingsModel = {
allowEditing: true,
allowAdding: true,
mode: 'Batch' // Edit mode: cell edit on double-click
};
data = [
{ OrderID: 10248, CustomerName: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Freight: 11.61 }
];
}Cell Events
Respond to cell interactions:
import { Component } from '@angular/core';
import { CellSelectEventArgs } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-cell-events-grid',
template: `
<div style="padding: 10px; background-color: #f0f0f0;">
<p>Selected Cell: {{ selectedCell }}</p>
</div>
<ejs-grid [dataSource]="data"
[allowSelection]="true"
(cellSelected)="onCellSelected($event)">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class CellEventsGridComponent {
selectedCell = 'None';
data = [
{ OrderID: 10248, CustomerName: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Freight: 11.61 }
];
onCellSelected(args: CellSelectEventArgs) {
this.selectedCell = `Row: ${args.rowIndex}, Column: ${args.columnIndex}, Value: ${args.value}`;
}
}Cell Styling
Style cells conditionally:
import { Component } from '@angular/core';
import { QueryCellInfoEventArgs } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-cell-styling-grid',
template: `
<style>
:host ::ng-deep .high-value {
background-color: #c8e6c9;
font-weight: 500;
}
:host ::ng-deep .low-value {
background-color: #ffcccc;
}
</style>
<ejs-grid [dataSource]="data"
(queryCellInfo)="onQueryCellInfo($event)">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class CellStylingGridComponent {
data = [
{ OrderID: 10248, CustomerName: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', Freight: 65.83 }
];
onQueryCellInfo(args: QueryCellInfoEventArgs) {
const data = args.data as any;
if (args.cell?.textContent && args.column?.field === 'Freight') {
if (data.Freight > 50) {
args.cell.classList.add('high-value');
} else if (data.Freight < 20) {
args.cell.classList.add('low-value');
}
}
}
}Clipboard
Table of Contents
When to Use This Skill
Use this skill when you need to:
- Copy/paste functionality — Enable users to copy grid cells and paste into other applications
- Excel integration — Allow copying grid data to Excel with formatting preserved
- Clipboard operations — Support Ctrl+C/Ctrl+V keyboard shortcuts for cell selection
- Multi-cell copying — Copy multiple selected cells or entire rows
- Formatted copying — Preserve headers, formatting, and structure when copying
- Paste with validation — Validate pasted data before updating the grid
- Export to external apps — Enable users to move grid data to spreadsheets or documents
- Batch data entry — Support pasting multiple rows at once for bulk operations
Overview
Clipboard functionality enables copy/paste operations for grid cells and rows. Users can copy data and paste into Excel, other applications, or back into the grid.
Enable Copy/Paste
Basic copy and paste functionality:
import { Component } from '@angular/core';
@Component({
selector: 'app-clipboard-grid',
template: `
<p style="padding: 10px; color: #666;">
Tip: Select cells and press Ctrl+C to copy, Ctrl+V to paste
</p>
<ejs-grid [dataSource]="data"
[allowSelection]="true"
[selectionSettings]="{ mode: 'Cell', type: 'Multiple' }">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class ClipboardGridComponent {
data = [
{ OrderID: 10248, CustomerName: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', Freight: 65.83 }
];
}Copy with Formatting
Copy cells with headers and formatting:
import { Component, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-clipboard-format-grid',
template: `
<div style="margin-bottom: 10px;">
<button (click)="copyWithHeaders()">Copy with Headers</button>
<button (click)="copySelectedRows()">Copy Selected Rows</button>
</div>
<ejs-grid #grid [dataSource]="data"
[allowSelection]="true"
[selectionSettings]="{ mode: 'Row', type: 'Multiple' }">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class ClipboardFormatGridComponent {
@ViewChild('grid') grid!: GridComponent;
data = [
{ OrderID: 10248, CustomerName: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', Freight: 65.83 }
];
copyWithHeaders() {
const selectedRows = this.grid.getSelectedRowIndexes();
let text = 'Order ID\tCustomer Name\tFreight\n';
selectedRows.forEach((index) => {
const row = this.grid.getCurrentViewRecords()[index];
text += `${row.OrderID}\t${row.CustomerName}\t${row.Freight}\n`;
});
navigator.clipboard.writeText(text);
console.log('Copied with headers');
}
copySelectedRows() {
const selectedRows = this.grid.getSelectedRowIndexes();
let text = '';
selectedRows.forEach((index) => {
const row = this.grid.getCurrentViewRecords()[index];
text += `${row.OrderID}\t${row.CustomerName}\t${row.Freight}\n`;
});
navigator.clipboard.writeText(text);
}
}Columns
Table of Contents
- When to Use This Skill
- Overview
- Column Properties
- Column Types
- Column Templates
- Foreign Key Columns
- Column Rendering
When to Use This Skill
Use this skill when you need to:
- Define grid structure — Map data fields to grid columns with appropriate types
- Configure column properties — Set widths, alignment, sorting, filtering capabilities
- Format column data — Display numbers as currency, dates with specific formats
- Create column templates — Customize column content with HTML templates
- Implement foreign keys — Display related data from lookup tables in columns
- Render custom content — Use templates for complex column visualizations
- Control column visibility — Show/hide columns based on conditions
- Set column types — Specify string, number, date, boolean types for proper handling
Overview
Columns define the structure and presentation of your grid data. Each column maps to a field in your data source and controls how that data is displayed, formatted, and interacted with.
Column Properties
Basic column configuration with essential properties:
import { Component } from '@angular/core';
@Component({
selector: 'app-column-grid',
template: `
<ejs-grid [dataSource]="data">
<e-columns>
<e-column field="EmployeeID" headerText="ID" [isPrimaryKey]="true"
width="100" type="number"></e-column>
<e-column field="FirstName" headerText="First Name" width="120"
[allowSorting]="true" [allowFiltering]="true"></e-column>
<e-column field="LastName" headerText="Last Name" width="120"></e-column>
<e-column field="Title" headerText="Title" width="150"
[customClass]="'custom-title'"></e-column>
<e-column field="HireDate" headerText="Hire Date" type="date"
format="yMd" width="130"></e-column>
<e-column field="Salary" headerText="Salary" type="number"
format="C2" width="120" [textAlign]="'Right'"></e-column>
<e-column field="Verified" headerText="Verified" type="boolean"
width="100"></e-column>
</e-columns>
</ejs-grid>
`
})
export class ColumnGridComponent {
data = [
{ EmployeeID: 1, FirstName: 'Nancy', LastName: 'Davolio', Title: 'Sales Representative',
HireDate: new Date(1992, 4, 1), Salary: 60000, Verified: true },
{ EmployeeID: 2, FirstName: 'Andrew', LastName: 'Fuller', Title: 'Vice President Sales',
HireDate: new Date(1992, 8, 14), Salary: 97000, Verified: true }
];
}Key column properties:
field— Maps to data source fieldheaderText— Display column headerwidth— Column width (px or %)type— Data type (string, number, date, boolean, datetime)format— Display format (C2 for currency, yMd for date)isPrimaryKey— Mark as primary keyallowSorting— Enable/disable sortingallowFiltering— Enable/disable filteringtextAlign— Align content (Left, Right, Center)
Column Types
Grid supports various data types with automatic formatting:
<ejs-grid [dataSource]="data">
<e-columns>
<!-- String column -->
<e-column field="Name" headerText="Name" type="string" width="120"></e-column>
<!-- Number column -->
<e-column field="Quantity" headerText="Quantity" type="number"
[customFormat]="numberFormat" width="100"></e-column>
<!-- Date column -->
<e-column field="OrderDate" headerText="Order Date" type="date"
format="yMd" width="130"></e-column>
<!-- DateTime column -->
<e-column field="CreatedAt" headerText="Created At" type="datetime"
format="g" width="180"></e-column>
<!-- Boolean column (checkbox) -->
<e-column field="IsActive" headerText="Active" type="boolean" width="100"></e-column>
</e-columns>
</ejs-grid>Column Templates
Use custom templates to render complex content in columns:
import { Component } from '@angular/core';
@Component({
selector: 'app-template-grid',
template: `
<ejs-grid [dataSource]="data">
<e-columns>
<e-column field="EmployeeID" headerText="ID" width="100"></e-column>
<e-column field="FirstName" headerText="First Name" width="120"></e-column>
<!-- Custom template with formatting -->
<e-column field="Salary" headerText="Salary" width="120">
<ng-template #template let-data>
<div style="color: #2ecc71; font-weight: bold;">
{{ data.Salary | currency:'USD':'symbol' }}
</div>
</ng-template>
</e-column>
<!-- Template with buttons -->
<e-column headerText="Actions" width="150" textAlign="Center">
<ng-template #template let-data>
<button (click)="onEdit(data)">Edit</button>
<button (click)="onDelete(data)">Delete</button>
</ng-template>
</e-column>
<!-- Image template -->
<e-column field="ProfilePicture" headerText="Profile" width="100">
<ng-template #template let-data>
<img [src]="data.ProfilePicture" width="30" height="30"
style="border-radius: 50%;">
</ng-template>
</e-column>
<!-- Badge template -->
<e-column field="Status" headerText="Status" width="100">
<ng-template #template let-data>
<span [ngClass]="{
'badge-success': data.Status === 'Active',
'badge-warning': data.Status === 'Inactive',
'badge-danger': data.Status === 'Suspended'
}">{{ data.Status }}</span>
</ng-template>
</e-column>
</e-columns>
</ejs-grid>
`
})
export class TemplateGridComponent {
data = [...]; // Your data
onEdit(data: any) { console.log('Edit:', data); }
onDelete(data: any) { console.log('Delete:', data); }
}Foreign Key Columns
Display foreign key relationships with lookup values:
import { Component } from '@angular/core';
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-fk-grid',
template: `
<ejs-grid [dataSource]="orderData">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<!-- Foreign Key Column -->
<e-column field="EmployeeID" headerText="Employee" width="150"
[foreignKeyValue]="'FirstName'"
[dataSource]="employeeManager">
</e-column>
<e-column field="CustomerID" headerText="Customer" width="150"
[foreignKeyValue]="'ContactName'"
[dataSource]="customerManager">
</e-column>
<e-column field="OrderDate" headerText="Order Date" type="date"
format="yMd" width="130"></e-column>
</e-columns>
</ejs-grid>
`
})
export class ForeignKeyGridComponent {
orderData = [
{ OrderID: 10248, EmployeeID: 1, CustomerID: 'VINET', OrderDate: new Date(1996, 6, 4) },
{ OrderID: 10249, EmployeeID: 3, CustomerID: 'TOMSP', OrderDate: new Date(1996, 6, 5) }
];
employeeManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor(),
crossDomain: true
});
customerManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor(),
crossDomain: true
});
}Column Rendering
Control how columns are rendered and displayed:
<ejs-grid [dataSource]="data" [columns]="columns">
<!-- Columns can be dynamically created -->
</ejs-grid>export class DynamicColumnGridComponent {
columns = [
{ field: 'OrderID', headerText: 'Order ID', width: 100 },
{ field: 'CustomerName', headerText: 'Customer Name', width: 150 },
{
field: 'OrderDate',
headerText: 'Order Date',
type: 'date',
format: 'yMd',
width: 130
},
{
field: 'Freight',
headerText: 'Freight',
type: 'number',
format: 'C2',
width: 120
}
];
data = [
{ OrderID: 10248, CustomerName: 'VINET', OrderDate: new Date(1996, 6, 4), Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', OrderDate: new Date(1996, 6, 5), Freight: 11.61 }
];
}Command Column in Angular Grid
Table of Contents
- When to Use This Skill
- Overview
- Enable Command Column
- Built-in Commands
- Custom Commands
- Command Events
- Conditional Commands
- Styling Commands
When to Use This Skill
Use this skill when you need to:
- Add action buttons to rows — Implement Edit, Delete, or custom action buttons
- Enable built-in commands — Use predefined Edit, Delete, Save, Cancel commands
- Create custom commands — Add application-specific button actions
- Handle command events — Respond to user clicks on command buttons
- Conditional command display — Show/hide commands based on row data or user permissions
- Style command buttons — Customize button appearance and behavior
- Batch row operations — Enable multiple row selections with command execution
- Simplify row interactions — Avoid custom templates for common row actions
Overview
Command column provides buttons for common grid actions like Edit, Delete, and custom operations. It simplifies row-level interactions without needing custom templates.
---
Enable Command Column
Basic Setup
import { Component } from '@angular/core';
import { EditService, ToolbarService } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-grid',
template: `
<ejs-grid [dataSource]="data"
[editSettings]="{ mode: 'Dialog', allowEditing: true, allowDeleting: true }">
<e-columns>
<e-column type="checkbox" width="50"></e-column>
<e-column field="OrderID" headerText="Order ID" width="100" [isPrimaryKey]="true"></e-column>
<e-column field="CustomerID" headerText="Customer" width="120"></e-column>
<e-column type="commandColumn" headerText="Actions" width="150"
[commands]="[{type: 'Edit', buttonOption: {cssClass: 'e-flat'}},
{type: 'Delete', buttonOption: {cssClass: 'e-flat'}},
{type: 'Save', buttonOption: {cssClass: 'e-flat'}},
{type: 'Cancel', buttonOption: {cssClass: 'e-flat'}}]">
</e-column>
</e-columns>
</ejs-grid>
`,
providers: [EditService, ToolbarService]
})
export class AppCommandGridComponent {
data = [];
}---
Built-in Commands
Available Built-in Commands
| Command | Available In | Action |
|---|---|---|
Edit | Normal mode | Start editing row |
Delete | Normal mode | Delete row |
Save | Edit mode | Save changes |
Cancel | Edit mode | Cancel editing |
Edit/Delete Commands
<e-column
headerText="Edit/Delete"
width="120"
[commands]="[
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Delete', buttonOption: { cssClass: 'e-flat' } }
]">
</e-column>Save/Cancel Commands
<e-column
headerText="Save/Cancel"
width="120"
[commands]="[
{ type: 'Save', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Cancel', buttonOption: { cssClass: 'e-flat' } }
]">
</e-column>Custom Commands
The custom command column feature extends the Grid component's capabilities by enabling custom command buttons in a column to perform specific actions on individual rows. To define custom command buttons, use the column.commands property. Associate the desired actions with these buttons through the commandClick event, allowing custom logic to be executed on button click.
View Details with Dialog
The following example demonstrates how to display a custom "Details" command button and handle the click event to show row information in a dialog:
import { GridModule, EditService, CommandColumnService } from '@syncfusion/ej2-angular-grids';
import { DialogModule } from '@syncfusion/ej2-angular-popups';
import { Component, OnInit, ViewChild } from '@angular/core';
import { CommandModel, CommandClickEventArgs, GridComponent, EditSettingsModel } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-custom-command',
standalone: true,
imports: [GridModule, DialogModule],
providers: [EditService, CommandColumnService],
template: `
<ejs-grid #grid [dataSource]="data" [editSettings]="editSettings"
(commandClick)="commandClick($event)" height="310px">
<e-columns>
<e-column field="OrderID" headerText="Order ID" textAlign="Right"
[isPrimaryKey]="true" width="100"></e-column>
<e-column field="CustomerID" headerText="Customer ID" width="120"></e-column>
<e-column field="Freight" headerText="Freight" textAlign="Right"
editType="numericedit" width="120" format="C2"></e-column>
<e-column field="ShipCountry" headerText="Ship Country"
editType="dropdownedit" width="150"></e-column>
<e-column headerText="Commands" width="140" [commands]="commands"></e-column>
</e-columns>
</ejs-grid>
<ejs-dialog #dialog header="Row Information" showCloseIcon="true"
width="400px" [visible]="dialogVisible" (close)="dialogClose()">
<ng-template>
<ng-container *ngIf="rowData">
<p><b>Order ID:</b> {{ rowData.OrderID }}</p>
<p><b>Customer ID:</b> {{ rowData.CustomerID }}</p>
<p><b>Freight:</b> {{ rowData.Freight }}</p>
<p><b>Ship Country:</b> {{ rowData.ShipCountry }}</p>
</ng-container>
</ng-template>
</ejs-dialog>
`
})
export class CustomCommandComponent implements OnInit {
@ViewChild('grid') gridComponent!: GridComponent;
data: any[] = [];
editSettings: EditSettingsModel = { allowEditing: true, allowDeleting: true };
commands: CommandModel[] = [];
dialogVisible: boolean = false;
rowData: any;
ngOnInit(): void {
this.loadData();
this.initializeCommands();
}
private loadData(): void {
// Load grid data from service
this.data = [
{ OrderID: 10248, CustomerID: 'VINET', Freight: 32.38, ShipCountry: 'France' },
{ OrderID: 10249, CustomerID: 'TOMSP', Freight: 11.61, ShipCountry: 'Germany' }
// ... more data
];
}
private initializeCommands(): void {
this.commands = [
{ buttonOption: { content: 'Details', cssClass: 'e-flat' } }
];
}
commandClick(args: CommandClickEventArgs): void {
// Get command type from commandColumn.type (Edit, Delete, Save, Cancel)
// For custom commands, type will be None/undefined
const commandType = args.commandColumn?.type;
const buttonText = args.target?.textContent?.trim();
if (commandType === 'Edit' || commandType === 'Delete') {
// Built-in commands are handled automatically by the grid
return;
}
// Handle custom command buttons
if (buttonText === 'Details') {
this.rowData = args.rowData;
this.dialogVisible = true;
}
}
dialogClose(): void {
this.dialogVisible = false;
}
}Command Events
Click Event
import { Component, ViewChild, OnInit } from '@angular/core';
import { GridComponent, CommandClickEventArgs } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-command-click-grid',
template: `
<ejs-grid #grid [dataSource]="data" (commandClick)="onCommandClick($event)">
<e-columns>
<!-- columns with commands -->
</e-columns>
</ejs-grid>
`
})
export class CommandClickGridComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
data: any[] = [];
ngOnInit() {
this.loadData();
}
loadData() {
this.data = [...]; // Load from service
}
onCommandClick(args: CommandClickEventArgs) {
console.log('Command type:', args.commandColumn?.type);
console.log('Row data:', args.rowData);
console.log('Button element:', args.target);
console.log('Button text:', args.target?.textContent);
}
}Command Type Checking
import { Component, ViewChild, OnInit } from '@angular/core';
import { GridComponent, CommandClickEventArgs } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-command-check-grid',
template: `
<ejs-grid #grid [dataSource]="data" (commandClick)="onCommand($event)">
<e-columns>
<!-- columns with commands -->
</e-columns>
</ejs-grid>
`
})
export class CommandCheckGridComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
data: any[] = [];
ngOnInit() {
this.loadData();
}
loadData() {
this.data = [...]; // Load from service
}
onCommand(args: CommandClickEventArgs) {
const commandType = args.commandColumn?.type;
const buttonText = args.target?.textContent?.trim();
if (commandType === 'Edit') {
console.log('Edit command:', args.rowData);
} else if (commandType === 'Delete') {
console.log('Delete command:', args.rowData);
} else {
// Custom commands identified by button text
console.log('Custom command:', buttonText, 'Data:', args.rowData);
}
}
}---
Conditional Commands
Show/Hide Based on Row Data
import { Component, ViewChild } from '@angular/core';
import { GridComponent, EditService } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-conditional-command',
template: `
<ejs-grid #grid [dataSource]="data" [editSettings]="editSettings">
<e-columns>
<e-column field="OrderID" headerText="Order ID" [isPrimaryKey]="true"></e-column>
<e-column field="CustomerID" headerText="Customer"></e-column>
<e-column field="Status" headerText="Status"></e-column>
<e-column headerText="Actions" width="150" [cellTemplate]="'actionTemplate'">
</e-column>
</e-columns>
<ng-template #actionTemplate let-data>
<button class="e-btn e-small" (click)="editRow(data)" [style.margin-right.px]="5">
Edit
</button>
<button
*ngIf="data.Status !== 'Completed"
class="e-btn e-small e-danger"
(click)="deleteRow(data)">
Delete
</button>
<button
*ngIf="data.Status === 'Pending"
class="e-btn e-small e-success"
(click)="completeRow(data)">
Complete
</button>
</ng-template>
</ejs-grid>
`,
providers: [EditService]
})
export class ConditionalCommandComponent {
@ViewChild('grid') gridComponent!: GridComponent;
data: any[] = [];
editSettings: any = { allowEditing: true, allowDeleting: true, mode: 'Dialog' };
editRow(rowData: any): void {
console.log('Edit row:', rowData);
}
deleteRow(rowData: any): void {
if (confirm('Are you sure you want to delete this record?')) {
this.gridComponent.deleteRecord();
}
}
completeRow(rowData: any): void {
console.log('Complete row:', rowData);
}Role-Based Commands
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
import { Component, ViewChild } from '@angular/core';
import { GridComponent, EditService } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-role-command',
template: `
<ejs-grid #grid [dataSource]="data" [editSettings]="editSettings">
<e-columns>
<e-column field="OrderID" headerText="Order ID" [isPrimaryKey]="true"></e-column>
<e-column field="CustomerID" headerText="Customer"></e-column>
<e-column
headerText="Actions"
width="150"
[commands]="getCommandsForRole()">
</e-column>
</e-columns>
</ejs-grid>
`,
providers: [EditService]
})
export class RoleCommandComponent {
@ViewChild('grid') gridComponent!: GridComponent;
data: any[] = [];
editSettings: any = { allowEditing: true, allowDeleting: true };
userRole: string = 'user';
ngOnInit() {
this.getUserRole();
}
private getUserRole(): void {
this.userRole = localStorage.getItem('userRole') || 'user';
}
getCommandsForRole(): any[] {
if (this.userRole === 'admin') {
return [
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Delete', buttonOption: { cssClass: 'e-flat' } },
{ buttonOption: { content: 'Audit', cssClass: 'e-flat', click: this.viewAudit } }
];
} else {
return [
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{ buttonOption: { content: 'View', cssClass: 'e-flat', click: this.viewDetails } }
];
}
}
private viewAudit = (args: any): void => {
console.log('Audit:', args.rowData);
};
private viewDetails = (args: any): void => {
console.log('Details:', args.rowData);
}Status-Based Commands
import { Component, ViewChild, OnInit } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-status-command-grid',
template: `
<ejs-grid #grid [dataSource]="data">
<e-columns>
<!-- columns -->
<e-column headerText="Actions" [commands]="getCommandsForStatus(row.Status)"></e-column>
</e-columns>
</ejs-grid>
`
})
export class StatusCommandGridComponent implements OnInit {
@ViewChild('grid') gridInstance: GridComponent;
data: any[] = [];
ngOnInit() {
this.loadData();
}
loadData() {
this.data = [...]; // Load from service
}
getCommandsForStatus(status: string) {
const baseCommands = [{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } }];
if (status === 'Draft') {
baseCommands.push({ type: 'Delete', buttonOption: { cssClass: 'e-flat' } });
}
if (status === 'Pending') {
baseCommands.push({
buttonOption: { content: 'Approve', cssClass: 'e-flat e-success', click: () => this.approveRow() }
});
}
if (status === 'Completed') {
baseCommands.push({
buttonOption: { content: 'Archive', cssClass: 'e-flat e-info', click: () => this.archiveRow() }
});
}
return baseCommands;
}
approveRow() {
console.log('Row approved');
}
archiveRow() {
console.log('Row archived');
}
}---
Styling Commands
Button Styling
<e-column
headerText="Actions"
width="200"
[commands]="[
{
type: 'Edit',
buttonOption: {
cssClass: 'e-flat e-outline',
iconCss: 'e-icons e-edit'
}
},
{
type: 'Delete',
buttonOption: {
cssClass: 'e-flat e-danger',
iconCss: 'e-icons e-delete'
}
},
{
buttonOption: {
content: 'Archive',
cssClass: 'e-flat e-warning',
iconCss: 'e-icons e-archive'
}
}
]">
</e-column>CSS Classes
/* Button sizes */
.e-btn.e-small { padding: 4px 8px; font-size: 12px; }
.e-btn.e-large { padding: 12px 16px; font-size: 16px; }
/* Button styles */
.e-btn.e-flat { background-color: transparent; border: 1px solid #ddd; }
.e-btn.e-outline { border: 2px solid currentColor; }
.e-btn.e-primary { background-color: #007bff; color: white; }
.e-btn.e-success { background-color: #28a745; color: white; }
.e-btn.e-danger { background-color: #dc3545; color: white; }
.e-btn.e-warning { background-color: #ffc107; color: black; }
.e-btn.e-info { background-color: #17a2b8; color: white; }
/* Hover effects */
.e-btn:hover { opacity: 0.8; cursor: pointer; }
.e-btn:disabled { opacity: 0.5; cursor: not-allowed; }Inline Styling
import { Component, ViewChild } from '@angular/core';
import { GridComponent, EditService } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-styled-command',
template: `
<ejs-grid #grid [dataSource]="data" [editSettings]="editSettings">
<e-columns>
<e-column field="OrderID" headerText="Order ID" [isPrimaryKey]="true"></e-column>
<e-column field="CustomerID" headerText="Customer"></e-column>
<e-column headerText="Actions" width="150" [cellTemplate]="'styledActionTemplate'">
</e-column>
</e-columns>
<ng-template #styledActionTemplate let-data>
<button
[ngStyle]="commandButtonStyle"
[style.background-color]="'#007bff'"
(click)="editRow(data)">
Edit
</button>
<button
[ngStyle]="commandButtonStyle"
[style.background-color]="'#dc3545'"
(click)="deleteRow(data)">
Delete
</button>
</ng-template>
</ejs-grid>
`,
providers: [EditService]
})
export class StyledCommandComponent {
@ViewChild('grid') gridComponent!: GridComponent;
data: any[] = [];
editSettings: any = { allowEditing: true, allowDeleting: true };
commandButtonStyle = {
padding: '6px 10px',
fontSize: '12px',
marginRight: '4px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
color: 'white'
};
editRow(rowData: any): void {
console.log('Edit row:', rowData);
}
deleteRow(rowData: any): void {
if (confirm('Are you sure?')) {
this.gridComponent.deleteRecord();
}
}Context Menu
Table of Contents
When to Use This Skill
Use this skill when you need to:
- Right-click menu options — Provide context menu for grid operations
- Custom context menus — Define custom menu items for specific actions
- Row operations — Implement Copy, Edit, Delete, and custom row actions
- Column operations — Add column-specific context menu items
- Conditional menu items — Show/hide menu items based on context
- Keyboard accessibility — Ensure menu works with keyboard navigation
- Multi-row operations — Apply batch operations via context menu
Overview
Context menu provides right-click options for grid operations. Customize menu items for editing, copying, and other actions.
Basic Context Menu
Enable context menu with default items:
import { Component } from '@angular/core';
@Component({
selector: 'app-context-menu-grid',
template: `
<ejs-grid [dataSource]="data"
[contextMenuItems]="['Copy', 'Edit', 'Delete']">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class ContextMenuGridComponent {
data = [
{ OrderID: 10248, CustomerName: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', Freight: 65.83 }
];
}Custom Context Menu
Create custom context menu with row and cell operations:
import { Component, ViewChild } from '@angular/core';
import { GridComponent, ContextMenuClickEventArgs } from '@syncfusion/ej2-angular-grids';
@Component({
selector: 'app-custom-context-menu-grid',
template: `
<ejs-grid #grid [dataSource]="data"
[contextMenuItems]="contextMenuItems"
[editSettings]="editSettings"
(contextMenuClick)="onContextMenuClick($event)">
<e-columns>
<e-column field="OrderID" headerText="Order ID" [isPrimaryKey]="true" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Status" headerText="Status" width="120"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class CustomContextMenuGridComponent {
@ViewChild('grid') grid!: GridComponent;
editSettings = {
allowEditing: true,
allowAdding: true,
allowDeleting: true,
mode: 'Normal'
};
contextMenuItems = [
{ text: 'Copy', target: '.e-gridcontent', id: 'grid_copy' },
{ text: 'Edit', target: '.e-gridcontent', id: 'grid_edit' },
{ text: 'Delete', target: '.e-gridcontent', id: 'grid_delete' },
{ text: 'Mark as Completed', target: '.e-gridcontent', id: 'custom_complete' },
{ text: 'View Details', target: '.e-gridcontent', id: 'custom_details' }
];
data = [
{ OrderID: 10248, CustomerName: 'VINET', Status: 'Pending', Freight: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', Status: 'In Progress', Freight: 11.61 },
{ OrderID: 10250, CustomerName: 'HANAR', Status: 'Completed', Freight: 65.83 }
];
onContextMenuClick(args: ContextMenuClickEventArgs) {
console.log('Context menu clicked:', args.item);
if ((args.item as any).id === 'custom_complete') {
this.markAsCompleted();
} else if ((args.item as any).id === 'custom_details') {
this.viewDetails();
}
}
markAsCompleted() {
const selectedRowIndex = this.grid.getSelectedRowIndexes()[0];
if (selectedRowIndex !== undefined) {
const recordObject = this.grid.getCurrentViewRecords()[selectedRowIndex];
recordObject.Status = 'Completed';
this.grid.refresh();
}
}
viewDetails() {
const selectedRowIndex = this.grid.getSelectedRowIndexes()[0];
if (selectedRowIndex !== undefined) {
const recordObject = this.grid.getCurrentViewRecords()[selectedRowIndex];
console.log('View details for:', recordObject);
}
}
}Data Binding
Table of Contents
- When to Use This Skill
- Overview
- Data Source Selection Rule
- Local Data Binding
- Remote Data Binding
- DataManager Configuration
- Dynamic Data Updates
When to Use This Skill
Use this skill when you need to:
- Bind local data — Display static arrays or client-side data in the grid
- Bind remote data — Connect grid to REST APIs or server endpoints
- Configure DataManager — Set up data sources with adaptors and parameters
- Implement dynamic updates — Update grid data in response to user actions
- Handle API responses — Transform and bind API data to grid
- Server-side operations — Configure filtering, sorting, paging on server
- Real-time data — Update grid with new data from backend
- Data source strategies — Choose between local array and remote DataManager
Overview
Data binding connects your data source to the grid. Syncfusion Grid supports multiple data binding approaches to handle various scenarios from static arrays to complex API integration.
⚠️ Data Source Selection Rule — Choose the Right Approach
Always default to local array data unless the prompt explicitly mentions a remote API, REST endpoint, or server-side data source.
| Scenario | Use | How |
|---|---|---|
| Prompt provides sample data, static list, or does NOT mention an API | Local array | [dataSource]="data" where data is a plain JS array |
| Prompt explicitly mentions REST API, remote endpoint, server-side, or URL | DataManager | dataManager = new DataManager({ url: '...', adaptor: new UrlAdaptor() }) |
- ✅ Local array: Used when data is hardcoded, provided inline, or when no remote source is mentioned. No imports from
@syncfusion/ej2-dataneeded. - ✅ DataManager: Used only when the prompt explicitly mentions connecting to a REST API, remote URL, or server-side data source.
- ❌ Never use
DataManagerwith a remote URL when the prompt asks for local/static data — it will cause data not to load because the URL does not exist.
Local Data Binding
Bind local array data directly to the grid:
import { Component } from '@angular/core';
interface Employee {
EmployeeID: number;
FirstName: string;
LastName: string;
Salary: number;
}
@Component({
selector: 'app-grid',
template: `
<ejs-grid [dataSource]="employees">
<e-columns>
<e-column field="EmployeeID" headerText="ID" width="100"></e-column>
<e-column field="FirstName" headerText="First Name" width="120"></e-column>
<e-column field="LastName" headerText="Last Name" width="120"></e-column>
<e-column field="Salary" headerText="Salary" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class GridComponent {
employees: Employee[] = [
{ EmployeeID: 1, FirstName: 'Nancy', LastName: 'Davolio', Salary: 60000 },
{ EmployeeID: 2, FirstName: 'Andrew', LastName: 'Fuller', Salary: 97000 },
{ EmployeeID: 3, FirstName: 'Janet', LastName: 'Leverling', Salary: 63000 }
];
}Remote Data Binding
Remote DataManager Quick Checklist
Only use DataManager when the prompt explicitly mentions a REST API, remote endpoint, or server-side data. For local/static data use a plain array.
1. Import DataManager and the appropriate adaptor from @syncfusion/ej2-data 2. Always replace the `url` with the actual API endpoint — never leave a placeholder or demo URL 3. Match `field` names on `e-column` exactly to the JSON property names returned by the API — any mismatch causes empty columns 4. Choose the correct adaptor based on your API response shape:
UrlAdaptor→ API returns{ result: [...], count: N }WebApiAdaptor→ API returns a plain array or{ value: [...], Count: N }
5. For paging with UrlAdaptor, the API must return a count field with the total record count — without it the pager shows incorrect page numbers 6. Add [loadingIndicator]="{ indicatorType: 'Spinner' }" or 'Shimmer' only when the prompt asks for a loading indicator or the scenario explicitly involves async API fetching — never add it for local data 7. With UrlAdaptor, all operations (filter, search, sort, page) are automatically sent server-side — inject FilterService, SearchService, SortService, PageService services as normal; no custom query logic needed 8. For server-side error handling use the actionFailure event.
Fetch data from an API or server:
import { Component, OnInit } from '@angular/core';
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { Query } from '@syncfusion/ej2-data';
@Component({
selector: 'app-remote-grid',
template: `
<ejs-grid [dataSource]="dataManager">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="Freight" headerText="Freight" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class RemoteGridComponent implements OnInit {
public dataManager: DataManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor(),
crossDomain: true
});
ngOnInit() {
// DataManager will automatically fetch data
}
}DataManager Configuration
Choosing the Right Adaptor
| Adaptor | Use When | Expected API Response |
|---|---|---|
UrlAdaptor | Your API is built to work with Syncfusion DataManager | { result: [...], count: N } |
WebApiAdaptor | Standard Web API returning plain arrays or OData-like responses | { value: [...], Count: N } or plain array |
ODataAdaptor | OData v3 service | OData XML/JSON format |
ODataV4Adaptor | OData v4 service | OData v4 JSON format |
JsonAdaptor | Local array already in memory | Plain JS array |
⚠️ If data is not loading, the most common causes are:
1. Wrong URL — the url is a placeholder or demo URL, not your real API2. Wrong adaptor — your API returns a plain array but you are usingUrlAdaptor(useWebApiAdaptorinstead)
3. Wrong field names —fieldvalues on<e-column>don't match JSON property names returned by the API
4. Missing `count` —UrlAdaptorrequires the response to include"count"for paging to work
Configure DataManager with custom settings for advanced scenarios:
import { Component } from '@angular/core';
import { DataManager, ODataV4Adaptor, Query } from '@syncfusion/ej2-data';
@Component({
selector: 'app-custom-data-grid',
template: `
<ejs-grid [dataSource]="dataManager" [allowPaging]="true">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="OrderDate" headerText="Order Date" type="date" format="yMd" width="130"></e-column>
</e-columns>
</ejs-grid>
`
})
export class CustomDataGridComponent {
public dataManager: DataManager = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor(),
pageSize: 12
});
}Dynamic Data Updates
Update grid data dynamically by modifying the data source:
import { Component, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
interface Order {
OrderID: number;
CustomerName: string;
TotalAmount: number;
}
@Component({
selector: 'app-dynamic-grid',
template: `
<button (click)="addOrder()">Add Order</button>
<button (click)="updateOrder()">Update First Order</button>
<button (click)="deleteOrder()">Delete Last Order</button>
<ejs-grid #grid [dataSource]="orders">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="100"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="TotalAmount" headerText="Total Amount" type="number" format="C2" width="120"></e-column>
</e-columns>
</ejs-grid>
`
})
export class DynamicGridComponent {
@ViewChild('grid') grid!: GridComponent;
orders: Order[] = [
{ OrderID: 10248, CustomerName: 'VINET', TotalAmount: 32.38 },
{ OrderID: 10249, CustomerName: 'TOMSP', TotalAmount: 11.61 }
];
addOrder() {
const newOrder: Order = {
OrderID: 10250,
CustomerName: 'HANAR',
TotalAmount: 65.83
};
this.orders = [...this.orders, newOrder];
}
updateOrder() {
if (this.orders.length > 0) {
this.orders[0].TotalAmount = 100;
this.orders = [...this.orders]; // Trigger change detection
}
}
deleteOrder() {
if (this.orders.length > 0) {
this.orders.pop();
this.orders = [...this.orders]; // Trigger change detection
}
}
}