# CoreUI Data Grid Angular documentation > High-performance Angular data grid for CoreUI — 100,000 rows with sorting, filtering, selection and pagination. --- # CoreUI Angular Data Grid > High-performance Angular data grid — 100,000 rows with sorting, filtering, selection, pagination, server-side data, column resizing, pinning, ordering and full theming. CoreUI Data Grid is a high-performance grid for displaying and interacting with large tabular datasets. It renders 100,000 rows in the browser with sorting, filtering, selection and pagination, and hands off to your API when the data outgrows the browser's memory. ## Why Data Grid - **Fast by default.** Row [virtualization](https://coreui.io/data-grid/angular/docs/features/virtualization/) keeps only the visible window in the DOM, so scrolling stays smooth at 100k rows. - **Headless core, styled shell.** A proven headless table engine and row virtualization under a CoreUI-themed UI. Drop to the [headless table](https://coreui.io/data-grid/angular/docs/api/headless/) any time. - **Complete feature set.** Sorting, [filtering](https://coreui.io/data-grid/angular/docs/features/filtering/), [selection](https://coreui.io/data-grid/angular/docs/features/row-selection/), [pagination](https://coreui.io/data-grid/angular/docs/features/pagination/), [server-side data](https://coreui.io/data-grid/angular/docs/features/server-side-data/), column [sizing](https://coreui.io/data-grid/angular/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/angular/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/), a [column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) and [CSV export](https://coreui.io/data-grid/angular/docs/features/csv-export/). - **Themeable.** Every knob is a `--cui-data-grid-*` CSS variable resolving through CoreUI semantic tokens, so light/dark theming works with no extra CSS. See [Styling & theming](https://coreui.io/data-grid/angular/docs/customization/styling/). - **Angular-native.** A standalone component with signal-based inputs and outputs, `OnPush` change detection and `ng-template` customization. - **Small.** Around 48 KB gzipped for the underlying grid core. ## How it's built The grid is a thin, styled layer over a headless table engine (state, sorting, filtering, pagination, pinning, ordering, visibility) and row virtualization (windowed rendering). Inputs are named after the features they control, outputs are verbs, and output payloads carry the grid's own state — a small, predictable API. ## Get started 1. [Install](https://coreui.io/data-grid/angular/docs/getting-started/installation/) the package. 2. Follow the [Quickstart](https://coreui.io/data-grid/angular/docs/getting-started/quickstart/) to render your first grid. 3. Browse the [feature matrix](https://coreui.io/data-grid/angular/docs/getting-started/features/) to see what's available. ## Packages | Package | Framework | Docs | | ------- | --------- | ---- | | `@coreui/data-grid` | Vanilla JavaScript | [coreui.io/data-grid/docs](https://coreui.io/data-grid/docs/) | | `@coreui/react-data-grid` | React | [coreui.io/data-grid/react/docs](https://coreui.io/data-grid/react/docs/) | | `@coreui/vue-data-grid` | Vue | [coreui.io/data-grid/vue/docs](https://coreui.io/data-grid/vue/docs/) | | `@coreui/angular-data-grid` | Angular | [coreui.io/data-grid/angular/docs](https://coreui.io/data-grid/angular/docs/) | --- # Angular Data Grid Installation > Install CoreUI Data Grid for Angular via npm and load the shared grid stylesheet. ## npm ```sh npm install @coreui/angular-data-grid ``` Data Grid layers on the `.table` styles from `@coreui/coreui` or `@coreui/coreui-pro` (≥ 5), so make sure one of them is loaded on the page. The grid's own stylesheet ships in the `@coreui/data-grid` package, installed automatically as a dependency. The `DataGridComponent` is standalone — import it straight into your component: ```ts import { DataGridComponent } from '@coreui/angular-data-grid' @Component({ imports: [DataGridComponent], // ... }) ``` Add the grid stylesheet to your global styles (e.g. `angular.json` `styles` or `styles.scss`): ```scss @use "@coreui/data-grid/dist/css/data-grid.css"; ``` ## Stylesheet The grid's CSS defines `--cui-data-grid-*` custom properties that resolve through CoreUI's semantic variables, so it inherits your theme (including `data-coreui-theme="dark"`) automatically. See [Styling & theming](https://coreui.io/data-grid/angular/docs/customization/styling/) for the full token reference. Next: the [Quickstart](https://coreui.io/data-grid/angular/docs/getting-started/quickstart/). --- # Angular Data Grid Quickstart > Render your first CoreUI Data Grid for Angular in a few lines — columns, data, a stable row key, then your first feature. This guide builds a working grid from scratch. It assumes you've [installed](https://coreui.io/data-grid/angular/docs/getting-started/installation/) `@coreui/angular-data-grid` and loaded the grid stylesheet. ## 1. The component `DataGridComponent` is standalone — import it and drop `` into your template: ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' @Component({ selector: 'app-users', imports: [DataGridComponent], template: `` }) export class UsersComponent {} ``` ## 2. Columns and data Define columns by `key` (the property to read from each item) and bind your `items`: ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' @Component({ selector: 'app-users', imports: [DataGridComponent], template: `` }) export class UsersComponent { readonly columns: DataGridColumn[] = [ { key: 'name', label: 'Name' }, { key: 'role', label: 'Role' } ] readonly items: DataGridItem[] = [ { id: 1, name: 'Alice', role: 'admin' }, { id: 2, name: 'Bob', role: 'editor' }, { id: 3, name: 'Carol', role: 'viewer' } ] readonly itemKey = (item: DataGridItem) => String(item.id) } ``` `itemKey` returns a stable id per row. It's optional, but [selection](https://coreui.io/data-grid/angular/docs/features/row-selection/) needs it to survive sorting and filtering — set it up front. ## 3. Turn on a feature Every feature is a single input. Add filtering and selection: ```html ``` Sorting is on by default. From here, explore the [feature matrix](https://coreui.io/data-grid/angular/docs/getting-started/features/) or jump to any feature page. ## 4. React to changes The grid emits [outputs](https://coreui.io/data-grid/angular/docs/api/events/) with structured state: ```html ``` ```ts import type { DataGridSelectionChangeEvent } from '@coreui/angular-data-grid' onSelectionChange({ selectedItems }: DataGridSelectionChangeEvent) { console.log(selectedItems) } ``` ## What's next - Handle large or remote data with [server-side data](https://coreui.io/data-grid/angular/docs/features/server-side-data/). - Customize cells with a column [`formatter` or a cell template](https://coreui.io/data-grid/angular/docs/columns/overview/). - Replace built-in chrome with [slot templates](https://coreui.io/data-grid/angular/docs/features/slots/) or drive the [headless table](https://coreui.io/data-grid/angular/docs/api/headless/) directly. --- # Angular Data Grid Features > A capability matrix of everything CoreUI Data Grid for Angular does today, with the input or output that turns each feature on and a link to its docs. Everything the Data Grid does today, the input (or output) that enables it, and where to read more. Features not listed here are on the [roadmap](https://coreui.io/data-grid/angular/docs/resources/roadmap/). ## Data & rendering | Feature | Input / Output | Docs | | --- | --- | --- | | Row virtualization | `virtualization` (on by default) | [Virtualization](https://coreui.io/data-grid/angular/docs/features/virtualization/) | | Pagination | `pagination` | [Pagination](https://coreui.io/data-grid/angular/docs/features/pagination/) | | Server-side data | `dataProvider` | [Server-side data](https://coreui.io/data-grid/angular/docs/features/server-side-data/) | | Row selection | `rowSelection` | [Row selection](https://coreui.io/data-grid/angular/docs/features/row-selection/) | ## Sorting & filtering | Feature | Input / Output | Docs | | --- | --- | --- | | Column sorting (multi-column) | `sorting` (on by default) | [Sorting](https://coreui.io/data-grid/angular/docs/features/sorting/) | | Per-column filter row | `columnFilters` | [Filtering](https://coreui.io/data-grid/angular/docs/features/filtering/) | | Global search | `globalFilter` | [Filtering](https://coreui.io/data-grid/angular/docs/features/filtering/) | | Custom filter UI / predicate | `cDataGridColumnFilter` template, `filterFn` (per column) | [Filtering](https://coreui.io/data-grid/angular/docs/features/filtering/) | ## Columns | Feature | Input / Output | Docs | | --- | --- | --- | | Custom cell formatting / rendering | `formatter` (per column), `cDataGridCell` template | [Columns overview](https://coreui.io/data-grid/angular/docs/columns/overview/) | | Column resizing | `columnSizing` | [Column sizing](https://coreui.io/data-grid/angular/docs/columns/sizing/) | | Column pinning | `columnPinning` | [Column pinning](https://coreui.io/data-grid/angular/docs/columns/pinning/) | | Column ordering (drag & drop) | `columnOrder` | [Ordering & visibility](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/) | | Column visibility | `columnVisibility` | [Ordering & visibility](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/) | | Column header menu | `columnMenu` | [Column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) | ## Customization & output | Feature | Input / Output / API | Docs | | --- | --- | --- | | Custom toolbar / pagination / empty state | `cDataGridSlot` template | [Slots](https://coreui.io/data-grid/angular/docs/features/slots/) | | CSV export | `exportCsv()`, `downloadCsv()` | [CSV export](https://coreui.io/data-grid/angular/docs/features/csv-export/) | | Theming (CSS variables) | `--cui-data-grid-*` | [Styling & theming](https://coreui.io/data-grid/angular/docs/customization/styling/) | | Localization (i18n) | `labels` | [Localization](https://coreui.io/data-grid/angular/docs/customization/localization/) | | Headless escape hatch | `grid.table` | [Headless table](https://coreui.io/data-grid/angular/docs/api/headless/) | --- # LLMs.txt > LLM-optimized documentation endpoints for CoreUI Angular Data Grid — llms.txt, llms-full.txt, and a Markdown version of every page. ## Introduction [llms.txt](https://llmstxt.org) is an emerging standard that helps AI models understand and navigate documentation. The CoreUI Angular Data Grid docs expose three LLM-friendly endpoints so assistants can retrieve accurate, up-to-date content straight from the source. For a richer, tool-based integration, see [MCP Server](https://coreui.io/data-grid/angular/docs/ai-tools/mcp/). ## /llms.txt A structured index of the documentation — every page as a titled, described link, grouped by section. It gives an LLM a compact map of what exists and where. [Open llms.txt](https://coreui.io/data-grid/angular/docs/llms.txt) ## /llms-full.txt The entire documentation concatenated into a single Markdown file, so a model can ingest the whole set in one request. [Open llms-full.txt](https://coreui.io/data-grid/angular/docs/llms-full.txt) ## Markdown version of any page Append `.md` to any documentation page URL to get its clean Markdown version, without the site chrome. For example: [/data-grid/angular/docs/features/sorting.md](https://coreui.io/data-grid/angular/docs/features/sorting.md) --- # MCP Server > Bring the CoreUI Angular Data Grid documentation into your AI coding assistant with the @coreui/docs-mcp Model Context Protocol server. ## Introduction [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that lets AI assistants connect to external tools and data sources. The **`@coreui/docs-mcp`** server gives your assistant direct access to the official CoreUI documentation, so it answers from the current docs instead of relying on stale training data. Point it at the CoreUI Angular Data Grid docs with the `--base-path` option shown below. It provides: - **Documentation pages** — getting started, features, columns, and API reference. - **Live content** — read on demand from `coreui.io`, always matching the latest release. - **Structured API** — options, events, and methods for the grid. The server runs locally over stdio via `npx` — no global install required. ## Installation ### Claude Code Add the server with the CLI, then start a new session and run `/mcp` to verify the connection: ```bash claude mcp add coreui-data-grid -s user -- npx -y @coreui/docs-mcp --framework angular --base-path /data-grid/angular/docs ``` ### Cursor Create `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` for global configuration): ```json { "mcpServers": { "coreui-data-grid": { "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "angular", "--base-path", "/data-grid/angular/docs"] } } } ``` ### VS Code Create `.vscode/mcp.json` in your project. Note that VS Code uses the `servers` key: ```json { "servers": { "coreui-data-grid": { "type": "stdio", "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "angular", "--base-path", "/data-grid/angular/docs"] } } } ``` ### Windsurf Edit `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "coreui-data-grid": { "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "angular", "--base-path", "/data-grid/angular/docs"] } } } ``` ### Claude Desktop Edit `claude_desktop_config.json` (Settings → Developer → Edit Config): ```json { "mcpServers": { "coreui-data-grid": { "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "angular", "--base-path", "/data-grid/angular/docs"] } } } ``` ### OpenAI Codex Add it with the CLI, or edit `~/.codex/config.toml` directly: ```bash codex mcp add coreui-data-grid -- npx -y @coreui/docs-mcp --framework angular --base-path /data-grid/angular/docs ``` ```toml [mcp_servers.coreui-data-grid] command = "npx" args = ["-y", "@coreui/docs-mcp", "--framework", "angular", "--base-path", "/data-grid/angular/docs"] ``` ## Tools Once connected, your assistant can call the following tools: | Tool | Description | | --- | --- | | `list_components` | List documentation pages, optionally filtered by section or a substring. | | `search_docs` | Search the documentation and return the best matching pages. | | `get_doc_page` | Fetch the full Markdown of a page by slug or URL. | | `get_component_api` | Get the structured API (options, events, methods) for the grid. | ## Configuration | Flag | Environment variable | Default | Description | | --- | --- | --- | --- | | `--framework ` | `COREUI_DOCS_FRAMEWORKS` | `bootstrap,react,vue` | Enabled editions (comma-separated). The first is the default for tools. | | `--base-url ` | `COREUI_DOCS_BASE_URL` | `https://coreui.io` | Origin of the CoreUI site. Override only for a staging or self-hosted mirror. | | `--base-path ` | `COREUI_DOCS_BASE_PATH` | `/{framework}/docs` | Path after the origin where the docs live; `{framework}` is substituted. | | `--docs-path ` | `COREUI_DOCS_PATHS` | — | Per-framework path overrides, `fw=path` comma-separated. | | `--ttl ` | `COREUI_DOCS_TTL_MINUTES` | `360` | Cache freshness window. | | — | `COREUI_DOCS_CACHE_DIR` | OS cache directory | On-disk cache location. | ## Example prompts Once installed, try asking your AI assistant: - "How do I enable row selection in CoreUI Angular Data Grid?" - "What options does CoreUI Angular Data Grid accept?" - "Show me the CoreUI Angular Data Grid pagination documentation." - "How do I export CoreUI Angular Data Grid data to CSV?" The package is open source and published as [`@coreui/docs-mcp`](https://www.npmjs.com/package/@coreui/docs-mcp). --- # Angular Data Grid Overview > A single kitchen-sink Angular Data Grid demo — 10,000 rows across fourteen columns with the toolbar, per-column filters, sizing, pinning, selection and a column chooser all turned on at once. This page is the kitchen-sink demo: one grid with every interaction feature turned on at once, so you can see how they compose. It renders **10,000 rows** across **fourteen columns** — four hidden by default — with the [toolbar](https://coreui.io/data-grid/angular/docs/features/toolbar/), [per-column filters](https://coreui.io/data-grid/angular/docs/features/filtering/), [column sizing](https://coreui.io/data-grid/angular/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/angular/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/), the [column menu](https://coreui.io/data-grid/angular/docs/columns/menu/), [row selection](https://coreui.io/data-grid/angular/docs/features/row-selection/), multi-column [sorting](https://coreui.io/data-grid/angular/docs/features/sorting/) and [pagination](https://coreui.io/data-grid/angular/docs/features/pagination/). Every one of these is a single input, documented on its own page — this demo just enables them together. ```ts import { Component } from '@angular/core' import { DataGridCellDirective, DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas', 'Roberts', 'Walker'] const departments = ['Engineering', 'Sales', 'Marketing', 'Support', 'Finance', 'People'] const roles = ['Manager', 'Lead', 'Senior', 'Junior', 'Contractor'] const statuses = ['active', 'invited', 'suspended'] const countries = ['Poland', 'Germany', 'France', 'Spain', 'Italy', 'United States', 'United Kingdom'] const cities = ['Warsaw', 'Berlin', 'Paris', 'Madrid', 'Rome', 'New York', 'London'] const badges: Record = { active: 'success', invited: 'info', suspended: 'danger' } const currency = (value: unknown) => Number(value).toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }) const date = (value: unknown) => new Date(value as string).toLocaleDateString('en-US') @Component({ selector: 'docs-data-grid-overview-example', imports: [DataGridCellDirective, DataGridComponent], template: ` {{ item.status }} ` }) export class DataGridOverviewExample { readonly badges = badges readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 72, hideable: false }, { key: 'name', label: 'Name', width: 180 }, { key: 'email', label: 'Email', width: 220 }, { key: 'department', label: 'Department', width: 150, filterType: 'select' }, { key: 'role', label: 'Role', width: 130, filterType: 'select' }, { key: 'status', label: 'Status', width: 130, filterType: 'select' }, { key: 'salary', label: 'Salary', width: 130, filterType: 'number', formatter: currency }, { key: 'rating', label: 'Rating', width: 110, filterType: 'number' }, { key: 'projects', label: 'Projects', width: 120, filterType: 'number' }, { key: 'country', label: 'Country', width: 160, filterType: 'select' }, { key: 'city', label: 'City', width: 150 }, { key: 'startDate', label: 'Started', width: 140, filterType: 'date', formatter: date }, { key: 'lastActive', label: 'Last active', width: 140, filterType: 'date', formatter: date }, { key: 'phone', label: 'Phone', width: 160 } ] readonly items: DataGridItem[] = Array.from({ length: 10000 }, (_, i) => ({ id: i + 1, name: `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}`, email: `user${i + 1}@example.com`, department: departments[i % departments.length], role: roles[i % roles.length], status: statuses[i % statuses.length], salary: 45000 + ((i % 60) * 1500), rating: ((i % 9) + 1) / 2, projects: (i % 24) + 1, country: countries[i % countries.length], city: cities[i % cities.length], startDate: new Date(2021, i % 12, (i % 28) + 1).toISOString(), lastActive: new Date(2026, i % 6, (i % 27) + 1).toISOString(), phone: `+1 555 ${String(1000 + (i % 9000))}` })) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` ## What's turned on | Input | Feature | | --- | --- | | `[toolbar]` | [Column chooser, CSV export and global search](https://coreui.io/data-grid/angular/docs/features/toolbar/) | | `[columnFilters]` + `filterType` | [Per-column typed filters](https://coreui.io/data-grid/angular/docs/features/filtering/) (text, number, date, select) | | `[columnSizing]` | [Drag-to-resize columns](https://coreui.io/data-grid/angular/docs/columns/sizing/) | | `[columnPinning]` | [`id` pinned to the left edge](https://coreui.io/data-grid/angular/docs/columns/pinning/) | | `[columnOrder]` | [Drag-and-drop column reordering](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/) | | `[columnVisibility]` | [Four columns hidden until you show them](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/) | | `[columnMenu]` | [Per-header sort / pin / hide menu](https://coreui.io/data-grid/angular/docs/columns/menu/) | | `[rowSelection]` | [Checkbox column with select-all](https://coreui.io/data-grid/angular/docs/features/row-selection/) | | `[sorting]="{ multiple: true }"` | [Shift-click multi-column sort](https://coreui.io/data-grid/angular/docs/features/sorting/) | | `[pagination]` | [Page size switcher](https://coreui.io/data-grid/angular/docs/features/pagination/) | ## Presentation `salary` and the two dates use a column [`formatter`](https://coreui.io/data-grid/angular/docs/columns/overview/) so the displayed value — and the [CSV export](https://coreui.io/data-grid/angular/docs/features/csv-export/) — reads as currency and localized dates. The `status` column uses a [`cDataGridCell` template](https://coreui.io/data-grid/angular/docs/columns/overview/) to draw a colored badge. Everything else is the raw value. To scale past what fits in the browser, keep this UI and hand data fetching off to your API with [server-side data](https://coreui.io/data-grid/angular/docs/features/server-side-data/). --- # Angular Data Grid Virtualization > Row virtualization renders only the visible window of rows, so the Angular Data Grid stays fast with 100,000 rows and beyond — sorting, filtering and selection run across the full dataset. Virtualization keeps the DOM small no matter how large the dataset is: only the rows currently in view (plus a small buffer) are rendered as real elements. Reach for it whenever you bind more rows than the browser can comfortably paint at once — a few thousand and up. It is **on by default** (`[virtualization]="true"`) and is mutually exclusive with [pagination](https://coreui.io/data-grid/angular/docs/features/pagination/). ## 100,000 rows, virtualized Only the visible window of rows exists in the DOM — scroll, sort, filter and select across the full dataset. This live demo runs on 100,000 generated rows. ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] const statuses = ['active', 'pending', 'banned'] @Component({ selector: 'docs-data-grid-virtual-example', imports: [DataGridComponent], template: ` ` }) export class DataGridVirtualExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name', width: 200 }, { key: 'email', label: 'Email', width: 260 }, { key: 'role', label: 'Role', width: 110 }, { key: 'status', label: 'Status', width: 110 }, { key: 'score', label: 'Score', width: 90 } ] readonly items: DataGridItem[] = Array.from({ length: 100_000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length], status: statuses[i % statuses.length], score: (i * 37) % 1000 } }) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` ## How it works The grid measures the scroll viewport and renders only the rows that intersect it. Two inputs tune the behavior: - `rowHeight` — the estimated row height in px (default `44`) the virtualizer uses to size the scroll area and decide how many rows fit. - `overscan` — extra rows rendered above and below the visible window (default `10`) to smooth fast scrolling. Raise it if you see blank rows while flinging; lower it to shave DOM nodes. Fixed row height is assumed in this release — dynamic per-row heights are on the [roadmap](https://coreui.io/data-grid/angular/docs/resources/roadmap/). For datasets larger than browser memory, hand paging to your backend with [server-side data](https://coreui.io/data-grid/angular/docs/features/server-side-data/). See the [Performance guide](https://coreui.io/data-grid/angular/docs/guides/performance/) for tuning advice. --- # Angular Data Grid Sorting > Sort the Angular Data Grid by clicking a header, add multi-column sorting with shift+click, and opt individual columns out. Sorting is **on by default**. Click a header to toggle ascending ↔ descending; the sort runs across the whole dataset, not just the visible window. Shift+click a second header to sort by more than one column at once. ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-sorting-example', imports: [DataGridComponent], template: ` ` }) export class DataGridSortingExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90, sortable: false }, { key: 'name', label: 'Name' }, { key: 'role', label: 'Role', width: 140 }, { key: 'score', label: 'Score', width: 120 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, role: roles[i % roles.length], score: (i * 37) % 1000 } }) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` ## Usage ```html ``` Bind an object to tune the behavior: | Key | Type | Default | Description | | --- | --- | --- | --- | | `multiple` | `boolean` | `true` | Allow sorting by more than one column with shift+click. | | `resetable` | `boolean` | `false` | Allow a third click to clear the column's sort. | Disable sorting for a single column with `sortable: false` in its [definition](https://coreui.io/data-grid/angular/docs/api/columns/), or turn it off entirely with `[sorting]="false"`. ## Sort icon visibility Only the **active** sort direction (the ascending/descending arrow) shows by default — the neutral, unsorted indicator stays hidden to keep headers clean. Set `sorterVisibility` to surface it on every sortable column: | Value | Behavior | | --- | --- | | `'always'` | The neutral icon is always visible (dimmed) on sortable columns. | | `'hover'` | The neutral icon appears when the header is hovered or focused. | ```html ``` ## Multi-column sorting With `multiple` enabled (the default), **shift+click** a second header to add it to the sort instead of replacing the first. The sort priority follows click order. Below, click **Department**, then shift+click **Salary** — the readout shows the active sort and its priority. ```ts import { Component, computed, signal } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem, DataGridSortingChangeEvent, SortingState } from '@coreui/angular-data-grid' const departments = ['Engineering', 'Design', 'Sales'] const labels: Record = { name: 'Name', department: 'Department', salary: 'Salary' } @Component({ selector: 'docs-data-grid-sorting-multi-example', imports: [DataGridComponent], template: `

{{ status() }}

` }) export class DataGridSortingMultiExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90, sortable: false }, { key: 'name', label: 'Name' }, { key: 'department', label: 'Department', width: 160 }, { key: 'salary', label: 'Salary', width: 140 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, department: departments[i % departments.length], salary: 40000 + ((i * 137) % 60000) })) readonly itemKey = (item: DataGridItem) => String(item.id) private readonly sorting = signal([]) readonly status = computed(() => { const sorting = this.sorting() return sorting.length ? `Sorted by: ${sorting .map((sort, index) => `${index + 1}. ${labels[sort.id]} ${sort.desc ? '↓' : '↑'}`) .join(' ')}` : 'Click a header, then shift+click another to sort by multiple columns.' }) onSortingChange({ sorting }: DataGridSortingChangeEvent) { this.sorting.set(sorting) } } ``` ## Reacting to sort changes Each change emits the `sortingChange` output with the grid's `{ sorting }` state: ```html ``` ```ts import type { DataGridSortingChangeEvent } from '@coreui/angular-data-grid' onSortingChange({ sorting }: DataGridSortingChangeEvent) { console.log(sorting) // [{ id: 'name', desc: false }] } ``` In [server-side mode](https://coreui.io/data-grid/angular/docs/features/server-side-data/) the same `sorting` state is handed to your `dataProvider` so your API does the ordering. --- # Angular Data Grid Filtering > Filter the Angular Data Grid with a per-column filter row, a global search input, custom filter UIs and custom matching predicates. The Data Grid filters on two levels. `[columnFilters]="true"` renders a filter row in the header with one input per filterable column; `[globalFilter]="true"` adds a single search input above the grid that matches across every column. Both narrow the rows client-side (or feed your [`dataProvider`](https://coreui.io/data-grid/angular/docs/features/server-side-data/) in server-side mode). Opt a column out with `filterable: false`. ```html ``` ## Filter menu With `columnFilters` enabled every filterable column gets a **filter button** (funnel icon, configurable via `filterIcon`) in its header, opening a filter dialog. The dialog offers typed operators driven by `column.filterType` — `text` (default), `number` and `date` get operator conditions (up to two, joined **AND**/**OR**), while `select` renders a faceted checkbox list of the column's actual values. An active filter marks the button with a dot; the structured value travels inside the grid's `columnFilters` state, so a [`dataProvider`](https://coreui.io/data-grid/angular/docs/features/server-side-data/) receives it verbatim. Try it below: filter *Salary* with `Between`, *Department* with the set filter, or combine two conditions on *Hired*. ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const departments = ['Engineering', 'Design', 'Sales', 'Support'] @Component({ selector: 'docs-data-grid-filter-menu-example', imports: [DataGridComponent], template: ` ` }) export class DataGridFilterMenuExample { readonly columns: DataGridColumn[] = [ { key: 'name', label: 'Name' }, { key: 'department', label: 'Department', filterType: 'select' }, { key: 'salary', label: 'Salary', filterType: 'number' }, { key: 'hired', label: 'Hired', filterType: 'date' } ] readonly items: DataGridItem[] = Array.from({ length: 200 }, (_, i) => ({ name: `Employee ${String(i + 1).padStart(3, '0')}`, department: departments[i % departments.length], salary: 40_000 + ((i * 977) % 60_000), hired: new Date(2020, i % 60, (i % 27) + 1).toISOString().slice(0, 10) })) } ``` ## Custom column filters Two per-column hooks customize filtering. An `` renders your own markup in a dedicated filter row (shown only for columns that define it), receiving the headless `column` (plus `table` and `labels` in the template context) — the `column` hands you `setFilterValue()`, `getFilterValue()` and `column.getFacetedUniqueValues()` for building selects from the actual data. `filterFn` swaps the matching logic (default: case-insensitive contains, or the [filter dialog](https://coreui.io/data-grid/angular/docs/features/filtering/#filter-menu)'s structured value) for your own predicate `(value, filterValue, item) => boolean` — with a custom UI it receives exactly what that UI committed. Here Role pairs a faceted select with an exact-match predicate, while Score just declares `filterType: 'number'` and gets the built-in numeric dialog. ```ts import { Component } from '@angular/core' import { DataGridColumnFilterDirective, DataGridComponent } from '@coreui/angular-data-grid' import type { Column, DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-custom-filters-example', imports: [DataGridColumnFilterDirective, DataGridComponent], template: ` ` }) export class DataGridCustomFiltersExample { readonly columns: DataGridColumn[] = [ { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 160, // Exact match - the default contains would also match partial values. filterFn: (value, filterValue) => value === filterValue }, { key: 'score', label: 'Score', width: 140, filterType: 'number' } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length], score: (i * 37) % 1000 } }) readonly itemKey = (item: DataGridItem) => String(item.id) roleOptions(column: Column): string[] { return [...column.getFacetedUniqueValues().keys()].map(String).toSorted() } onRoleFilterChange(column: Column, event: Event) { const { value } = event.target as HTMLSelectElement column.setFilterValue(value === '' ? undefined : value) } } ``` ## Custom filter API | Column key / template | Type | Description | | --- | --- | --- | | `filterable` | `boolean` | Set `false` to remove the column's filter button. | | `` | context: `$implicit: column`, `table`, `labels` | Custom filter UI rendered in the filter row instead of the filter button (requires `columnFilters`). | | `filterFn` | `(value, filterValue, item) => boolean` | Custom predicate replacing the built-in matching; with the filter dialog it receives the structured value. | Every filter change emits the `filterChange` output with `{ columnFilters, globalFilter }`. A custom [toolbar slot](https://coreui.io/data-grid/angular/docs/features/slots/) that hosts its own search box still needs `[globalFilter]="true"` for the query to reach the grid. --- # Angular Data Grid Row Selection > Add a checkbox column to the Angular Data Grid with select-all and shift+click range selection, keyed to a stable row id so selection survives sorting, filtering and paging. `rowSelection` adds a checkbox column with a select-all header and shift+click range selection. Selection is keyed by [`itemKey`](https://coreui.io/data-grid/angular/docs/api/options/), so a selected row stays selected as the user sorts, filters or pages — set `itemKey` whenever you enable selection. Select a few rows below, then page or sort — the selection holds. ```ts import { Component, signal } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem, DataGridSelectionChangeEvent } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-row-selection-example', imports: [DataGridComponent], template: `

@if (selectedCount(); as count) { {{ count }} {{ count === 1 ? 'row' : 'rows' }} selected } @else { No rows selected }

` }) export class DataGridRowSelectionExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }) readonly itemKey = (item: DataGridItem) => String(item.id) readonly selectedCount = signal(0) onSelectionChange({ selectedItems }: DataGridSelectionChangeEvent) { this.selectedCount.set(selectedItems.length) } } ``` ## Usage ```html ``` Bind an object to configure it: | Key | Type | Default | Description | | --- | --- | --- | --- | | `selectAll` | `boolean` | `true` | Show the select-all checkbox in the header. | The [pagination demo](https://coreui.io/data-grid/angular/docs/features/pagination/) shows selection in action alongside a custom actions column. ## Reading the selection Listen for the `selectionChange` output: ```html ``` ```ts import type { DataGridSelectionChangeEvent } from '@coreui/angular-data-grid' onSelectionChange({ selectedItems, rowSelection }: DataGridSelectionChangeEvent) { console.log(selectedItems) console.log(rowSelection) // row-selection state } ``` You can also read it imperatively through the [headless table](https://coreui.io/data-grid/angular/docs/api/headless/) — `grid.table.getSelectedRowModel().rows.map((row) => row.original)`. ## With pinning and server-side data When a column is pinned left and selection is on, the checkbox column travels with it — see [Column pinning](https://coreui.io/data-grid/angular/docs/columns/pinning/). In [server-side mode](https://coreui.io/data-grid/angular/docs/features/server-side-data/), selection is id-keyed so it survives page changes, but `selectedItems` contains only the items present in the current page's data. --- # Angular Data Grid Keyboard Navigation > APG grid keyboard navigation for the Angular Data Grid — role="grid", a roving-tabindex active cell and arrow-key movement across header and data cells. `cellNavigation` switches the grid to the ARIA [grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/): the table gains `role="grid"`, exactly one cell is tabbable at a time (a roving tabindex), and the arrow keys move a visible active cell across header and data cells. Click a cell below, then navigate with the keyboard. ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-keyboard-navigation-example', imports: [DataGridComponent], template: ` ` }) export class DataGridKeyboardNavigationExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ] readonly items: DataGridItem[] = Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` ## Usage ```html ``` `cellNavigation` is off by default — without it the grid keeps native table semantics and the [accessible chrome](https://coreui.io/data-grid/angular/docs/guides/accessibility/) it always had. [Inline editing](https://coreui.io/data-grid/angular/docs/features/editing/) requires the active-cell model, so `editing` enables `cellNavigation` automatically. ## Keys | Key | Action | | --- | --- | | Arrow keys | Move one cell; stop at the edges (no wrap). The header label row is row one — Arrow Up from the first data row reaches it. | | Home / End | First / last cell in the row. | | Ctrl+Home / Ctrl+End | First / last data cell of the grid. | | PageUp / PageDown | Move one viewport when virtualized; move one page (and flip it) under pagination. | | Tab / Shift+Tab | Leave the grid — the whole grid is a single tab stop. | | Enter | Toggle sort on a sortable header cell; descend into a data cell's interactive content (links, buttons); start [editing](https://coreui.io/data-grid/angular/docs/features/editing/) an editable cell. | | Escape | Ascend from cell content back to the cell. | | Space | Toggle [row selection](https://coreui.io/data-grid/angular/docs/features/row-selection/) on the active row. | ## Focus and virtualization The active cell is state, not DOM: with [virtualization](https://coreui.io/data-grid/angular/docs/features/virtualization/) on, navigating to an off-window row scrolls it into view first and focuses it once it renders. If the focused row is recycled out of the window while you scroll, focus parks on the viewport and the next navigation key brings the active cell back into view. ## Styling The active cell shows an inset focus ring driven by two component tokens: ```css c-data-grid { --cui-data-grid-focus-ring-width: 2px; --cui-data-grid-focus-ring-color: var(--cui-primary); } ``` --- # Angular Data Grid Inline Editing > Inline cell editing for the Angular Data Grid — built-in text, number and select editors, validation, and a popup contract for rich editors like date pickers and multi-selects. `editing` turns cells editable in place. Press Enter or F2 on the active cell — or double-click any cell — to start; Enter commits, Escape cancels, Tab commits and moves to the next editable cell. Editing builds on [keyboard navigation](https://coreui.io/data-grid/angular/docs/features/keyboard-navigation/), so `editing` enables `cellNavigation` automatically. ```ts import { Component, signal } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridEditCommitEvent, DataGridItem } from '@coreui/angular-data-grid' const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-editing-example', imports: [DataGridComponent], template: ` ` }) export class DataGridEditingExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name', editable: true, editValidate: value => (value === '' ? 'Name is required' : true) }, { key: 'age', label: 'Age', width: 110, editable: { type: 'number', min: 0, max: 120 } }, { key: 'role', label: 'Role', width: 130, editable: { type: 'select', options: roles } } ] readonly items = signal( Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: 20 + (i % 40), role: roles[i % roles.length] })) ) readonly itemKey = (item: DataGridItem) => String(item.id) // The grid never mutates items - apply the committed change yourself. onEditCommit({ item, columnId, value }: DataGridEditCommitEvent) { this.items.update(current => current.map(row => (row.id === item.id ? { ...row, [columnId]: value } : row)) ) } } ``` ## Usage Editing is opt-in per column — `editable` picks a built-in editor, a custom `cDataGridCellEditor` template is itself the opt-in: ```ts columns: DataGridColumn[] = [ { key: 'name', editable: true }, // text input { key: 'age', editable: { type: 'number', min: 0 } }, // number input { key: 'role', editable: { type: 'select', options: ['admin', 'user'] } }, ] ``` ```html ``` ## The app owns the data The grid never mutates `items`. A commit emits `editCommit` with `{ item, columnId, value, previousValue }` — apply the change to your state and the grid re-renders (server-side, PATCH and refetch): ```html ``` ```ts onEditCommit({ item, columnId, value }: DataGridEditCommitEvent) { this.items.update((current) => current.map((row) => (row['id'] === item['id'] ? { ...row, [columnId]: value } : row)) ) } ``` `editStart` and `editCancel` fire around it with `{ item, columnId }`. ## Validation `editValidate` gates the commit. Return `true` to accept, or a message to block it — the editor gets `aria-invalid`, the `is-invalid` class and an `aria-errormessage` pointing at the message: ```ts { key: 'name', editable: true, editValidate: (value, item) => value !== '' || 'Name is required' } ``` An invalid value keeps the editor open; Escape still cancels. ## Custom editors A `cDataGridCellEditor` template replaces the built-in input with your own UI. It renders with the editing context; push draft values into the session — the Enter/Tab/blur/outside commits pick up the latest one: ```html ``` The template context is `{ $implicit: item, column, value, invalid, labels, session }`; `session.setValue(value)` updates the draft, `session.commit(value)` / `session.cancel()` end the edit directly. ## Rich editors — date pickers, autocompletes, multi-selects Editors whose UI extends beyond the cell — a date range picker's calendar, a multi-select's listbox — set `editorPopup: true` on the column. The grid renders the template in a `.data-grid-editor-popup` layer anchored to the cell (min-width = cell width), so a tall editor never reflows the row, and the cell keeps its content underneath. Three rules make components like `@coreui/angular-pro`'s date range picker, time picker and multi-select work as editors: - **Commit on outside, not on blur alone.** Picking a date in a calendar blurs the input mid-edit — so the grid commits on pointerdown *outside the edit scope* (the cell and the popup layer). Keep the component's overlay inside the popup rather than portaling it to `body`. - **One Escape, one layer.** An Escape the editor consumed (calling `preventDefault()` to close its own overlay) never cancels the edit — only the next, unconsumed Escape does. - **Values are not scalars.** The draft can be anything — a range picker pushes `{ startDate, endDate }` through `session.setValue`, a multi-select pushes an array; `editCommit` passes it through untouched. ```html ``` ## Interaction details - Scrolling the editing row out of the [virtualized](https://coreui.io/data-grid/angular/docs/features/virtualization/) window commits the draft; so do sort, filter and page transitions. - Replacing `items` and [server-side](https://coreui.io/data-grid/angular/docs/features/server-side-data/) data loads cancel an in-flight edit — the incoming data owns the cell. - Shift+Tab commits and moves to the previous editable cell. --- # Angular Data Grid Undo & Redo > Undo and redo inline-edit commits in the Angular Data Grid — toolbar buttons and Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y, with the app staying the single source of truth. `history` keeps an undo/redo stack of [inline editing](https://coreui.io/data-grid/angular/docs/features/editing/) commits. Undo with the toolbar button or Ctrl+Z (⌘Z on macOS), redo with Ctrl+Shift+Z or Ctrl+Y. Edit a few cells below, then undo your way back. ```ts import { Component, signal } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridEditCommitEvent, DataGridItem } from '@coreui/angular-data-grid' const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-history-example', imports: [DataGridComponent], template: ` ` }) export class DataGridHistoryExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name', editable: true }, { key: 'age', label: 'Age', width: 110, editable: { type: 'number', min: 0, max: 120 } }, { key: 'role', label: 'Role', width: 130, editable: { type: 'select', options: roles } } ] readonly items = signal( Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: 20 + (i % 40), role: roles[i % roles.length] })) ) readonly itemKey = (item: DataGridItem) => String(item.id) // Undo/redo re-emit editCommit with the values swapped, so the handler // that applies an edit also reverts it. onEditCommit({ item, columnId, value }: DataGridEditCommitEvent) { this.items.update(current => current.map(row => (row.id === item.id ? { ...row, [columnId]: value } : row)) ) } } ``` ## Usage ```html ``` The buttons stay disabled while their stack is empty; each undo/redo announces through the ARIA live region (`undoneAnnouncement`/`redoneAnnouncement` labels). `grid.undo()` / `grid.redo()` are also callable on the component. ## How undo works — the app stays in charge The grid never mutates `items`. An undo **re-emits `editCommit`** with `value` and `previousValue` swapped (redo re-emits the original), so the same handler that applied the edit reverts it — no second code path: ```ts onEditCommit({ item, columnId, value }: DataGridEditCommitEvent) { this.items.update((current) => current.map((row) => (row.id === item.id ? { ...row, [columnId]: value } : row)) ) } ``` History entries reference rows by id, so they survive the immutable updates this pattern produces. If a row disappears from the data entirely, its entries are dropped. New commits clear the redo stack; the stack holds the last 100 edits. Following MUI and AG Grid, undo/redo covers **data edits** — view changes (sorting, filters, column layout) are not tracked; persist those with [`stateKey`](https://coreui.io/data-grid/angular/docs/features/state/) instead. --- # Angular Data Grid Save & Restore State > Persist the Angular Data Grid view — sorting, filters, column order, sizing, visibility, pinning, selection and page — to localStorage with a single input. `stateKey` makes the grid's view persistent: every user-adjustable state slice — sorting, column filters and the global search, column order, sizing, visibility and pinning, row selection and the page — **autosaves to `localStorage`** under the key (debounced) and **restores automatically on init**. No buttons, nothing to wire up. Sort or resize below, reload the page — the grid comes back as you left it. ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-state-example', imports: [DataGridComponent], template: ` ` }) export class DataGridStateExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ] readonly items: DataGridItem[] = Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` ## Usage ```html ``` Every state change writes the snapshot (debounced at 250 ms) to `localStorage` under `coreui-data-grid:`; the next grid initialized with the same key restores it before first render. ## Reading the snapshot The saved snapshot is `{ sorting, columnFilters, globalFilter, columnOrder, columnPinning, columnSizing, columnVisibility, pagination, rowSelection }` (the `DataGridState` type). For your own storage, collect the slices from the `(xxxChange)` outputs — or read them off the public `table` — and hand a saved snapshot back by initializing the grid with `stateKey` pointing at it. ## What is (and isn't) state - Row selection restores by row id — set [`itemKey`](https://coreui.io/data-grid/angular/docs/api/options/) or the restored ids point at positions, not rows. - In [server-side mode](https://coreui.io/data-grid/angular/docs/features/server-side-data/) the restored sorting/filters/page are applied **before the first request**, so the grid loads straight into the saved view. - Scroll position and an in-flight [edit](https://coreui.io/data-grid/angular/docs/features/editing/) draft are not state. Data edits aren't either — undoing those is what [undo & redo](https://coreui.io/data-grid/angular/docs/features/history/) is for. - Restoring emits the matching `(xxxChange)` outputs for every slice that changed — your app sees a restore exactly like user interaction. --- # Angular Data Grid Pagination > Page through the Angular Data Grid with a page-size selector, range info and CoreUI pagination controls instead of virtualized scrolling. Pagination breaks the dataset into fixed-size pages with familiar Previous/Next controls. Use it when users expect discrete pages, when you want a predictable page height on the screen, or as the client-side counterpart to [server-side data](https://coreui.io/data-grid/angular/docs/features/server-side-data/) (which always paginates). Pagination is **mutually exclusive with [virtualization](https://coreui.io/data-grid/angular/docs/features/virtualization/)** — turning it on switches the grid out of windowed scrolling. ## Pagination mode Instead of virtualization the grid can paginate — with a page-size selector, range info and CoreUI `.pagination` controls. This demo also shows a custom `formatter`, an actions column built with the `cDataGridCell` template, and row selection. ```ts import { Component } from '@angular/core' import { DataGridCellDirective, DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-pagination-example', imports: [DataGridCellDirective, DataGridComponent], template: ` ` }) export class DataGridPaginationExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110, formatter: value => String(value).toUpperCase() }, { key: 'actions', label: '', sortable: false, filterable: false, width: 120 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }) readonly itemKey = (item: DataGridItem) => String(item.id) edit(item: DataGridItem) { alert(`Edit ${item.name} (#${item.id})`) } } ``` ## Options Bind `[pagination]="true"` for the defaults, or an object to configure it: | Key | Type | Default | Description | | --- | --- | --- | --- | | `pageSize` | `number` | `10` | Rows per page. | | `pageSizeOptions` | `number[]` | `[5, 10, 20, 50]` | Choices in the page-size selector. | | `position` | `'top' \| 'bottom' \| 'both'` | `'bottom'` | Where the pagination bar renders. | | `info` | `boolean` | `true` | Shows the `Showing X–Y of Z` range summary. | Every page change emits the `paginationChange` output with the grid's `{ pagination }` state. Drive paging yourself through the [headless table](https://coreui.io/data-grid/angular/docs/api/headless/) — e.g. `grid.table.setPageIndex(3)`. --- # Angular Data Grid Server-Side Data > Delegate sorting, filtering and pagination to your API with a single dataProvider function — the Angular Data Grid fetches one debounced request per state change and drops stale responses. Client-side mode ends where the browser's memory does. When your dataset lives in a database with tens of thousands of rows or more, hand the work to your backend: set `dataProvider` and the grid stops computing row models locally and asks your API for each page instead. This is the single biggest adoption unlocker for large data. ## Server-side data Set `dataProvider` and the grid switches to server-side mode — every sorting, filtering or pagination change triggers one debounced request (stale responses are dropped automatically), a loading overlay covers the viewport and totals come from your `totalRows`. Server-side mode implies pagination. This live demo hits CoreUI's public demo API (`apitest.coreui.io/demos/users`) — 10,000+ records sorted, filtered and paged server-side. ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridDataRequest, DataGridItem } from '@coreui/angular-data-grid' @Component({ selector: 'docs-data-grid-server-example', imports: [DataGridComponent], template: ` ` }) export class DataGridServerExample { readonly columns: DataGridColumn[] = [ { key: 'first_name', label: 'First name' }, { key: 'last_name', label: 'Last name' }, { key: 'email', label: 'Email' }, { key: 'country', label: 'Country' }, { key: 'ip_address', label: 'IP' } ] readonly itemKey = (item: DataGridItem) => String(item.id) readonly dataProvider = async ({ sorting, columnFilters, pagination }: DataGridDataRequest) => { const params = new URLSearchParams({ offset: String(pagination.pageIndex * pagination.pageSize), limit: String(pagination.pageSize) }) for (const { id, value } of columnFilters) { params.append(id, String(value)) } const [sort] = sorting if (sort) { params.append('sort', `${sort.id}%${sort.desc ? 'desc' : 'asc'}`) } const response = await fetch(`https://apitest.coreui.io/demos/users?${params}`) const result = await response.json() const totalRows = Number(result.number_of_matching_records) return { items: totalRows ? result.records : [], totalRows } } } ``` ## The dataProvider contract ```html ``` ```ts import type { DataGridDataRequest } from '@coreui/angular-data-grid' readonly dataProvider = async ({ sorting, columnFilters, globalFilter, pagination }: DataGridDataRequest) => { // fetch from your API and return the matching page return { items, totalRows } } ``` - **One request per state change.** `sorting`, `columnFilters`, `globalFilter` and `pagination` arrive as structured state; return the page of `items` plus the full `totalRows` count so the pager can render. - **Debounced & race-safe.** Rapid changes coalesce into one request and only the latest response is applied — stale ones are dropped by request id. - **Loading UX.** A `.data-grid-loading` overlay with a spinner covers the viewport and `aria-busy` is set while a request is in flight (`labels.loading`). - **Errors.** A rejected fetch emits the `dataError` output `{ error }`, shows the empty state (`labels.loadError`) and leaves the grid interactive. - **Success.** Each load emits the `dataLoad` output `{ items, totalRows }`. ## Selection semantics `rowSelection` is keyed by [`itemKey`](https://coreui.io/data-grid/angular/docs/api/options/), so a selection survives page changes by design. The `selectionChange` output's `selectedItems` contains only the items present in the current page's data — the grid does not cache full objects for pages it has scrolled past. The same page-bound rule applies to [CSV export](https://coreui.io/data-grid/angular/docs/features/csv-export/): in server-side mode every scope (`filtered`, `all`, `selected`) exports the currently loaded page only. When the [filter menu](https://coreui.io/data-grid/angular/docs/features/filtering/) is used, a column's entry in `columnFilters` carries the structured value — `{ conditions: [{ operator, value, value2? }], join: 'and' | 'or' }` for typed operators or `{ operator: 'in', value: [...] }` for the set filter — instead of a plain string. Servers should handle both shapes. --- # Angular Data Grid Toolbar > Add a built-in Angular Data Grid toolbar with a column chooser, CSV export button and global search — enabled with a single input or configured granularly. The `toolbar` input adds a built-in chrome row above the grid with a **column chooser**, a **CSV export** button, **Undo/Redo** buttons (with [`history`](https://coreui.io/data-grid/angular/docs/features/history/)) and a **global search** input. Each action drives an existing feature, so the toolbar is the ready-made UI you would otherwise build with the [`toolbar` slot](https://coreui.io/data-grid/angular/docs/features/slots/). ```html ``` `[toolbar]="true"` enables each action whose underlying feature is on: **columns** needs [`columnVisibility`](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/), **export** is always available, **undo/redo** needs [`history`](https://coreui.io/data-grid/angular/docs/features/history/), and **search** turns on the global filter. Pass a granular object to pick actions individually — `search: true` is the same input as [`[globalFilter]="true"`](https://coreui.io/data-grid/angular/docs/features/filtering/), so keep using whichever reads better. ```html ``` ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-toolbar-example', imports: [DataGridComponent], template: ` ` }) export class DataGridToolbarExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90, hideable: false }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` ## Column chooser With `toolbar.columns` enabled the columns button opens a popup listing every leaf column in visual order with a checkbox. Toggling a checkbox calls `column.toggleVisibility()` live — there is no Apply step. Columns marked `hideable: false` stay checked and disabled. The footer offers **Show all** and **Reset** (Reset restores the initial `columnVisibility` object). Visibility changes emit the `visibilityChange` output just like the [column menu](https://coreui.io/data-grid/angular/docs/columns/menu/). ## Export The export button downloads the current view as CSV by calling [`downloadCsv({ scope: 'filtered' })`](https://coreui.io/data-grid/angular/docs/features/csv-export/). Pass a [`CsvDownloadOptions`](https://coreui.io/data-grid/angular/docs/features/csv-export/) object as `toolbar.export` to set `filename`, `delimiter`, `bom`, `sanitize` or `scope`. ## Icons The toolbar buttons use the CoreUI icon set, overridable per-instance with the `toolbarColumnsIcon`, `toolbarExportIcon`, `toolbarUndoIcon` and `toolbarRedoIcon` inputs (an `` like every other icon override). ## Custom toolbar The `toolbar` slot still **replaces** the whole toolbar; the built-in buttons are not composable into a custom slot. For a fully custom toolbar — including a hand-built column chooser — use the [`toolbar` slot](https://coreui.io/data-grid/angular/docs/features/slots/) with the headless `table` and public helpers (`downloadCsv`, `column.toggleVisibility`). See the [column ordering & visibility](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/) page for a slot-based chooser. --- # Angular Data Grid Slots & Custom Rendering > Replace the Angular Data Grid's toolbar, pagination and empty-state chrome with your own markup through cDataGridSlot templates. Slots let you swap the grid's built-in chrome — `toolbar`, `pagination` and `empty` — for your own UI while the grid keeps driving state through the headless table. Reach for a slot when the default control isn't enough: a custom toolbar with extra actions, a bespoke pager, or a richer empty state. For custom *cell* content, use a column's [`formatter` or a `cDataGridCell` template](https://coreui.io/data-grid/angular/docs/columns/overview/) instead. ## Custom slots Replace the grid's chrome — `toolbar`, `pagination` and `empty` — with your own markup. Each slot is an `` receiving the headless `table` (implicit) and `labels` in its context and renders in place of the built-in module; it re-renders with the grid, so it always reflects current state. This demo swaps the built-in pagination for a minimal Previous/Next control driven through the headless `table`. ```ts import { Component } from '@angular/core' import { DataGridComponent, DataGridSlotDirective } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-slots-example', imports: [DataGridComponent, DataGridSlotDirective], template: `
Page {{ table.getState().pagination.pageIndex + 1 }} of {{ table.getPageCount() }} · {{ table.getRowCount() }} items
` }) export class DataGridSlotsExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` With `pagination.position: 'both'` the same template renders once per position. A custom toolbar that hosts its own global search still needs `[globalFilter]="true"` for the query to reach the grid. The `empty` slot also renders when a server load fails; use the `dataError` output to tell the two apart. ## Slot contract | Slot | Replaces | Template | | --- | --- | --- | | `toolbar` | The toolbar above the grid | `` | | `pagination` | The pagination bar | `` | | `empty` | The no-rows / load-error state | `` | - `$implicit` — the headless [`table`](https://coreui.io/data-grid/angular/docs/api/headless/); read current state from it (e.g. `let-table`). - `labels` — the merged UI strings, for translatable custom chrome. - The template re-renders on every grid state change, so it never goes stale; Angular's lifecycle handles cleanup. --- # Angular Data Grid CSV Export > Export Angular Data Grid rows to an RFC-4180 CSV string or file, with scope, delimiter, BOM and formula-injection sanitization options. Give users a one-click download of what they're looking at. `exportCsv()` returns a spec-compliant CSV string and `downloadCsv()` saves it as a file, both respecting the grid's current layout and filters. The pure CSV helper also ships standalone for server-side or headless use with no runtime dependencies. ## CSV export `exportCsv(table, options)` returns an RFC-4180 string and `downloadCsv(table, options)` saves it as a file — both take the headless `table` (the `table` property of the grid component, e.g. via `viewChild(DataGridComponent)`). Exported columns follow the rendered layout (pinning, order, visibility). Values use each column's `formatter` (never the cell template); `scope` picks `'filtered'` (default, all matching rows), `'all'` (ignores filters) or `'selected'`; `delimiter` and `bom` (Excel-friendly UTF-8) are configurable. Set `sanitize: true` to guard against CSV formula injection (prefixes fields starting with `=`, `+`, `-` or `@` with an apostrophe) when exporting untrusted data. Server-side grids export the rows currently in memory. Both helpers are published at `@coreui/data-grid/csv` — no runtime dependencies — and re-exported from `@coreui/angular-data-grid`. ```ts import { Component, viewChild } from '@angular/core' import { DataGridComponent, downloadCsv } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-csv-example', imports: [DataGridComponent], template: `
` }) export class DataGridCsvExample { private readonly grid = viewChild.required(DataGridComponent) readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110, formatter: value => String(value).toUpperCase() } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }) readonly itemKey = (item: DataGridItem) => String(item.id) export() { downloadCsv(this.grid().table, { filename: 'users.csv', bom: true }) } } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `scope` | `'filtered' \| 'all' \| 'selected'` | `'filtered'` | Which rows to export. | | `delimiter` | `string` | `','` | Field delimiter. | | `bom` | `boolean` | `false` | Prepend a UTF-8 BOM for Excel. | | `sanitize` | `boolean` | `false` | Prefix formula-triggering fields (`= + - @`) with `'` to prevent CSV injection. | | `filename` | `string` | `'export.csv'` | `downloadCsv` only — the download filename. | --- # Angular Data Grid Columns Overview > Define CoreUI Data Grid for Angular columns — keys, labels, cheap value formatting with formatter, and rich cell content with cDataGridCell templates. Columns are defined by the [`columns`](https://coreui.io/data-grid/angular/docs/api/options/) array. Each entry maps a `key` in your data to a header and a cell. This page covers the essentials; per-column features live on their own pages: [sizing](https://coreui.io/data-grid/angular/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/angular/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/) and the [column menu](https://coreui.io/data-grid/angular/docs/columns/menu/). ## Defining columns ```ts readonly columns: DataGridColumn[] = [ { key: 'name', label: 'Name' }, { key: 'email', label: 'Email' }, { key: 'role', label: 'Role' } ] ``` ```html ``` `key` is the property read from each item and doubles as the column id. `label` is the header text — it falls back to `key` when omitted. ## Formatting values Use `formatter` to transform the displayed value. It's cheap and stays on the scroll hot path, so it's the right tool for dates, numbers and currency: ```ts { key: 'createdAt', label: 'Created', formatter: (value) => new Date(String(value)).toLocaleDateString() } ``` `formatter` output is also what [CSV export](https://coreui.io/data-grid/angular/docs/features/csv-export/) writes. ## Rich cell content Use an `` for full custom cell content — action buttons, badges, links. The template receives the item (implicit), `index` and `value` in its context and is **never** used for CSV export: ```html ``` Import `DataGridCellDirective` alongside `DataGridComponent` to use the template. Use `formatter` **or** a cell template per column — `formatter` for values on the hot path, the template for interactive content. See the [column API](https://coreui.io/data-grid/angular/docs/api/columns/) for every key. --- # Angular Data Grid Column Sizing > Add drag-to-resize handles to Angular Data Grid header cells, with live or on-release width updates and per-column opt-out. Let users widen a column to read long values or shrink one they don't care about. `columnSizing` adds a drag handle to every resizable header cell; widths persist in the grid's state and can be committed live or on release. ## Column resizing Set `columnSizing` to add a drag handle to the right edge of every header cell. Widths follow the pointer live (`[columnSizing]="{ mode: 'onEnd' }"` commits them on release instead), the grid scrolls horizontally once the columns outgrow the viewport, and `column.width` seeds the starting width. Opt a column out with `resizable: false` — here the `#` column stays fixed. ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-resizing-example', imports: [DataGridComponent], template: ` ` }) export class DataGridResizingExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90, resizable: false }, { key: 'name', label: 'Name', width: 220 }, { key: 'email', label: 'Email', width: 280 }, { key: 'role', label: 'Role', width: 160 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnSizing` | `boolean \| { mode?: 'onChange' \| 'onEnd' }` | `false` | Enables resize handles. `mode` controls whether widths update while dragging (`'onChange'`, the default) or on release (`'onEnd'`). | | `resizable` (column) | `boolean` | `true` | Set `false` to drop the resize handle for a column. | | `width` (column) | `number` | — | Seeds the starting width in pixels, e.g. `200`. | Resizing emits the `sizingChange` output with the grid's `{ columnSizing }` state. --- # Angular Data Grid Column Pinning > Freeze Angular Data Grid columns to the left or right edge so they stay visible while the rest of the grid scrolls horizontally. Keep an identifier or an actions column in view while users scroll a wide grid sideways. Pinning freezes columns against the left or right edge with sticky positioning; everything else scrolls between them. ## Column pinning Freeze columns against the left or right edge with `[columnPinning]="{ left, right }"` — they stay put while the rest of the grid scrolls horizontally. Pass `[columnPinning]="true"` on its own to enable the feature and pin later through `grid.table.setColumnPinning(...)`. When a column is pinned left and selection is on, the checkbox column travels with it. The last left and first right column cast a shadow over the scrolling content. Pinning physically moves the column to its edge — the rendered order is always left-pinned, center, right-pinned. ```ts import { Component } from '@angular/core' import { DataGridCellDirective, DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const countries = ['Poland', 'Germany', 'France', 'Spain', 'Italy'] @Component({ selector: 'docs-data-grid-pinning-example', imports: [DataGridCellDirective, DataGridComponent], template: ` ` }) export class DataGridPinningExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 80 }, { key: 'firstName', label: 'First name', width: 160 }, { key: 'lastName', label: 'Last name', width: 160 }, { key: 'email', label: 'Email', width: 260 }, { key: 'country', label: 'Country', width: 200 }, { key: 'actions', label: '', sortable: false, width: 120 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, firstName: firstNames[i % firstNames.length], lastName: lastNames[i % lastNames.length], email: `${firstNames[i % firstNames.length].toLowerCase()}${i}@example.com`, country: countries[i % countries.length] })) readonly itemKey = (item: DataGridItem) => String(item.id) edit(item: DataGridItem) { alert(`Edit ${item.firstName} (#${item.id})`) } } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnPinning` | `boolean \| { left?: string[], right?: string[] }` | `false` | Freezes columns (by `key`) to the left/right edge. `true` enables the feature with no initial pins. | Pinning emits the `pinningChange` output with the grid's `{ columnPinning }` state. Reordering via [column ordering](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/) never crosses a pinning boundary. --- # Angular Data Grid Column Ordering & Visibility > Let users reorder Angular Data Grid columns by drag-and-drop and hide or show columns through a column chooser. Give users control over the layout: drag headers to reorder columns and toggle columns on and off to focus on what matters. Both features are driven through the headless table, so you can wire them into your own toolbar — as the column chooser in this demo does. ## Column ordering & visibility `columnOrder` makes headers draggable — drop one onto another to reorder (dragging never crosses a pinning boundary); pass an array for an initial order. `columnVisibility` enables hiding and showing columns through the headless table (`column.toggleVisibility()`, `column.getIsVisible()`); pass an object like `{ email: false }` to start with a column hidden. Opt a column out with `movable: false` / `hideable: false`. This demo adds a column chooser built entirely with the `toolbar` slot template. ```ts import { Component } from '@angular/core' import { DataGridComponent, DataGridSlotDirective } from '@coreui/angular-data-grid' import type { Column, DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-order-visibility-example', imports: [DataGridComponent, DataGridSlotDirective], template: `
@for (column of table.getAllLeafColumns(); track column.id) { @if (column.getCanHide()) { } }
` }) export class DataGridOrderVisibilityExample { readonly columns: DataGridColumn[] = [ // movable/hideable: false keeps the key column in place { key: 'id', label: '#', width: 90, movable: false, hideable: false }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }) readonly itemKey = (item: DataGridItem) => String(item.id) toggleColumn(column: Column, event: Event) { column.toggleVisibility((event.target as HTMLInputElement).checked) } } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnOrder` | `boolean \| string[]` | `false` | Drag-and-drop reordering; an array sets the initial order. Never crosses a pinning boundary. | | `columnVisibility` | `boolean \| Record` | `false` | Enables hiding/showing columns; an object sets the initial visibility. | | `movable` (column) | `boolean` | `true` | Set `false` to exclude a column from reordering. | | `hideable` (column) | `boolean` | `true` | Set `false` to prevent hiding a column. | Reordering emits the `orderChange` output `{ columnOrder }`; toggling visibility emits the `visibilityChange` output `{ columnVisibility }`. The [column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) offers a keyboard-accessible Move left/right as an alternative to drag-and-drop. --- # Angular Data Grid Column Menu > Add a per-column header menu to the Angular Data Grid that gathers sort, pin, move and hide actions behind one accessible ⋮ button, and customize its items with a builder. The column menu collects a column's actions — sort, pin, move, hide — behind a single ⋮ button in the header, so users don't have to discover drag-and-drop. It is the keyboard-accessible path to sorting, reordering and pinning, and it only shows the actions you've actually enabled. ## Column header menu `columnMenu` adds a ⋮ button to each header cell with the column's actions in one place — Sort ascending/descending/Unsort, Pin left/right/Unpin, Move left/right and Hide column. Items appear only for enabled features (`sorting`, `columnPinning`, `columnOrder`, `columnVisibility`) and respect per-column `sortable`/`movable`/`hideable` opt-outs; a column with no available action gets no button. Related actions are separated into groups by a divider. The menu follows the ARIA menu pattern (arrow keys, Home/End, Escape restores focus) — Move left/right is the keyboard-accessible way to reorder columns, complementing drag & drop. All labels are translatable via `labels`. ```ts import { Component } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridItem } from '@coreui/angular-data-grid' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-column-menu-example', imports: [DataGridComponent], template: ` ` }) export class DataGridColumnMenuExample { readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90, movable: false, hideable: false }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 140 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }) readonly itemKey = (item: DataGridItem) => String(item.id) } ``` ## Customize the menu Bind a **builder function** to `columnMenu` to take full control of the items. It receives the column and the built-in actions for that column, and returns the final list — so you can reorder, drop or add items, per column: ```ts columnMenu: ({ column, defaultActions }) => DataGridMenuAction[] ``` - **Reorder** — return `defaultActions` in a different order. - **Hide an item** — filter it out by `key` (`'sort-asc'`, `'sort-desc'`, `'clear-sort'`, `'pin-left'`, `'pin-right'`, `'unpin'`, `'move-left'`, `'move-right'`, `'hide'`). - **Add your own** — push a new action object. - **Per column** — branch on `column` (the [column definition](https://coreui.io/data-grid/angular/docs/api/columns/)). A column whose builder returns at least one action shows the ⋮ button, even with no built-in feature enabled. Each action has this shape: ```ts { key: string, // unique id, also used to filter built-ins label: string, // menu item text icon?: TemplateRef, // optional icon template disabled?: boolean, // render but don't run group?: string, // items with different groups get a divider between them run: () => void // invoked on click; the menu closes first } ``` Unlike the vanilla and React grids there are no SVG strings or sanitizer here: icon overrides are Angular templates. Pass a `` (a `TemplateRef`) to any of the icon inputs — `columnMenuIcon`, `sortAscendingIcon`, `sortDescendingIcon`, `sortNeutralIcon`, `pinLeftIcon`, `pinRightIcon`, `unpinIcon`, `moveLeftIcon`, `moveRightIcon`, `hideColumnIcon` — to replace a built-in icon, and use the same `TemplateRef` for a custom action's `icon`. The example below declares an `` and reads it with `viewChild.required`, then appends a "Copy header" action that copies the column's label to the clipboard. ```ts import { Component, TemplateRef, viewChild } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' import type { DataGridColumn, DataGridColumnMenuBuilder, DataGridItem } from '@coreui/angular-data-grid' const roles = ['admin', 'editor', 'viewer'] @Component({ selector: 'docs-data-grid-column-menu-custom-example', imports: [DataGridComponent], template: ` ` }) export class DataGridColumnMenuCustomExample { private readonly copyIcon = viewChild.required>('copyIcon') readonly columns: DataGridColumn[] = [ { key: 'id', label: '#', width: 90, movable: false, hideable: false }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 140 } ] readonly items: DataGridItem[] = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) readonly itemKey = (item: DataGridItem) => String(item.id) // A builder receives the built-in actions plus the column, and returns the // final list — filter items, reorder them, or add your own. Custom icons are // passed as a TemplateRef, like the grid's own menu icons. readonly columnMenu: DataGridColumnMenuBuilder = ({ column, defaultActions }) => [ ...defaultActions, { key: 'copy-header', label: 'Copy header', group: 'custom', icon: this.copyIcon(), run: () => navigator.clipboard?.writeText(column.label ?? column.key) } ] } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnMenu` | `boolean \| ((ctx) => DataGridMenuAction[])` | `false` | Adds a per-column header menu. `true` builds it from the enabled features (sort/pin/move/hide); a builder `({ column, defaultActions }) => actions` returns the final item list (see [Customize the menu](#customize-the-menu)). | Every menu and header icon is overridable with its own `TemplateRef` input (``): `columnMenuIcon`, `sortAscendingIcon`, `sortDescendingIcon`, `sortNeutralIcon`, `pinLeftIcon`, `pinRightIcon`, `unpinIcon`, `moveLeftIcon`, `moveRightIcon` and `hideColumnIcon`. They default to the CoreUI icon set. The default menu's items depend on which features are on ([sorting](https://coreui.io/data-grid/angular/docs/features/sorting/), [pinning](https://coreui.io/data-grid/angular/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/)). Its labels come from [`labels`](https://coreui.io/data-grid/angular/docs/customization/localization/); see [Accessibility](https://coreui.io/data-grid/angular/docs/guides/accessibility/) for the keyboard model. --- # Angular Data Grid Styling & Theming > Theme the CoreUI Data Grid for Angular with --cui-data-grid-* CSS variables that resolve through CoreUI semantic tokens, so light and dark modes work with no extra CSS. The Data Grid is styled entirely with CSS custom properties. Every knob is a `--cui-data-grid-*` variable that resolves through CoreUI's semantic tokens (`--cui-body-bg`, `--cui-border-color`, …), so the grid inherits your theme — including `data-coreui-theme="dark"` — with no extra rules. ## Light & dark mode Because the tokens resolve through CoreUI semantic variables, dark mode works out of the box: ```html
``` No grid-specific dark styles are needed — the semantic tokens flip and the grid follows. ## Overriding tokens Set any variable on the grid element (or an ancestor) to retheme it: ```css c-data-grid { --cui-data-grid-spacing: 0.75rem; --cui-data-grid-header-bg: var(--cui-tertiary-bg); --cui-data-grid-viewport-max-height: 40rem; } ``` ## CSS variables | Variable | Default | Controls | | --- | --- | --- | | `--cui-data-grid-viewport-max-height` | `30rem` | Max height of the scroll viewport. | | `--cui-data-grid-header-bg` | `var(--cui-body-bg, #fff)` | Header cell background. | | `--cui-data-grid-header-shadow` | `inset 0 calc(-1 * var(--cui-border-width, 1px)) 0 var(--cui-border-color, #dee2e6)` | Header bottom border. | | `--cui-data-grid-select-cell-width` | `2.5rem` | Width of the selection checkbox column. | | `--cui-data-grid-sorter-margin` | `.25rem` | Gap before the sort indicator. | | `--cui-data-grid-sorter-opacity` | `.3` | Resting opacity of the sort indicator. | | `--cui-data-grid-spacing` | `.5rem` | Cell padding. | | `--cui-data-grid-resizer-width` | `.625rem` | Hit area of the resize handle. | | `--cui-data-grid-resizer-grip-height` | `60%` | Height of the resize grip. | | `--cui-data-grid-resizer-grip-color` | `var(--cui-border-color, #dee2e6)` | Resize grip color. | | `--cui-data-grid-resizer-grip-color-hover` | `var(--cui-secondary-color, #6c757d)` | Resize grip color on hover. | | `--cui-data-grid-pinned-shadow-width` | `.375rem` | Width of the shadow cast by pinned columns. | | `--cui-data-grid-pinned-shadow` | `rgba(0, 0, 0, .15)` | Color of the pinned-column shadow. | | `--cui-data-grid-drop-indicator` | `var(--cui-primary, #321fdb)` | Drop indicator while reordering columns. | | `--cui-data-grid-loading-bg` | — | Background of the loading overlay (server-side mode). | | `--cui-data-grid-menu-bg` | `var(--cui-body-bg, #fff)` | Column menu background. | | `--cui-data-grid-menu-border-color` | `var(--cui-border-color, #dee2e6)` | Column menu border. | | `--cui-data-grid-menu-shadow` | `var(--cui-box-shadow, 0 .5rem 1rem rgba(0, 0, 0, .15))` | Column menu shadow. | | `--cui-data-grid-menu-min-width` | `10rem` | Column menu minimum width. | | `--cui-data-grid-menu-item-hover-bg` | `var(--cui-tertiary-bg, #f8f9fa)` | Column menu item hover background. | The grid also inherits the `.table` styles (striping, borders) from `@coreui/coreui(-pro)`, so table-level CSS variables like `--cui-table-striped-bg` apply too. --- # Angular Data Grid Localization > Translate every CoreUI Data Grid for Angular UI string through the labels input, with {token} interpolation for dynamic values. Every UI string the grid renders — menu items, pagination labels, ARIA announcements — comes from the `labels` input. Bind your own strings and they're merged over the defaults, so you only override what you need. ## Usage ```html ``` ```ts import type { DataGridLabels } from '@coreui/angular-data-grid' readonly labels: Partial = { globalFilterPlaceholder: 'Szukaj…', pageSizeLabel: 'Wierszy na stronę', itemsInfo: '{first}–{last} z {total}' } ``` ## Interpolation Labels with `{token}` placeholders are interpolated at render time — unknown tokens are left untouched. Available tokens: `{column}` (column label), `{first}`, `{last}`, `{total}` (pagination range) and `{count}` (results announcement). ## Default labels | Key | Default | Used by | | --- | --- | --- | | `addCondition` | `Add condition` | [Filter menu](https://coreui.io/data-grid/angular/docs/features/filtering/) | | `applyFilter` | `Apply` | [Filter menu](https://coreui.io/data-grid/angular/docs/features/filtering/) | | `clearFilter` | `Clear filter` | [Filter menu](https://coreui.io/data-grid/angular/docs/features/filtering/) + quick-input clear | | `clearSort` | `Unsort` | [Column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) sort actions | | `columnMenu` | `Column options for {column}` | [Column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) button | | `filterAction` | `Filter…` | [Column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) action | | `filterColumn` | `Filter {column}` | [Filter](https://coreui.io/data-grid/angular/docs/features/filtering/) input | | `filterSummaryConditions` | `{count} conditions` | Quick-input summary | | `filterSummarySelected` | `{count} selected` | Quick-input summary | | `firstPage` | `First page` | [Pagination](https://coreui.io/data-grid/angular/docs/features/pagination/) | | `globalFilterLabel` | `Search` | Global search accessible label | | `globalFilterPlaceholder` | `Search` | Global search placeholder | | `hideColumn` | `Hide column` | Column menu | | `itemsInfo` | `{first}–{last} of {total}` | Pagination range summary | | `joinAnd` | `AND` | [Filter menu](https://coreui.io/data-grid/angular/docs/features/filtering/) | | `joinOr` | `OR` | [Filter menu](https://coreui.io/data-grid/angular/docs/features/filtering/) | | `lastPage` | `Last page` | Pagination | | `loadError` | `Failed to load data` | [Server-side](https://coreui.io/data-grid/angular/docs/features/server-side-data/) error state | | `loading` | `Loading…` | Server-side loading overlay | | `moveLeft` | `Move left` | Column menu | | `moveRight` | `Move right` | Column menu | | `nextPage` | `Next page` | Pagination | | `operatorBetween` | `Between` | Filter menu operators | | `operatorBlank` | `Blank` | Filter menu operators | | `operatorContains` | `Contains` | Filter menu operators | | `operatorEndsWith` | `Ends with` | Filter menu operators | | `operatorEquals` | `Equals` | Filter menu operators | | `operatorGreaterThan` | `Greater than` | Filter menu operators | | `operatorGreaterThanOrEqual` | `Greater than or equal` | Filter menu operators | | `operatorLessThan` | `Less than` | Filter menu operators | | `operatorLessThanOrEqual` | `Less than or equal` | Filter menu operators | | `operatorNotBlank` | `Not blank` | Filter menu operators | | `operatorNotContains` | `Does not contain` | Filter menu operators | | `operatorNotEquals` | `Not equals` | Filter menu operators | | `operatorStartsWith` | `Starts with` | Filter menu operators | | `pageSizeLabel` | `Rows per page` | Pagination page-size selector | | `paginationLabel` | `Pagination` | Pagination nav accessible label | | `pinLeft` | `Pin left` | Column menu | | `pinRight` | `Pin right` | Column menu | | `previousPage` | `Previous page` | Pagination | | `resetColumns` | `Reset` | [Toolbar](https://coreui.io/data-grid/angular/docs/features/toolbar/) column chooser footer | | `redoneAnnouncement` | `Change redone` | [Undo & redo](https://coreui.io/data-grid/angular/docs/features/history/) ARIA live announcement | | `resetColumns` | `Reset` | [Toolbar](https://coreui.io/data-grid/angular/docs/features/toolbar/) column chooser | | `resultsAnnouncement` | `{count} results` | ARIA live announcement | | `searchValues` | `Search values` | Set filter search box | | `selectAllRows` | `Select all rows` | [Selection](https://coreui.io/data-grid/angular/docs/features/row-selection/) header checkbox | | `selectAllValues` | `Select all` | Set filter | | `selectRow` | `Select row` | Selection row checkbox | | `showAllColumns` | `Show all` | [Toolbar](https://coreui.io/data-grid/angular/docs/features/toolbar/) column chooser footer | | `sortAscending` | `Sort ascending` | [Column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) sort actions | | `sortDescending` | `Sort descending` | [Column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) sort actions | | `toolbarColumns` | `Columns` | [Toolbar](https://coreui.io/data-grid/angular/docs/features/toolbar/) column chooser button | | `toolbarExport` | `Export` | [Toolbar](https://coreui.io/data-grid/angular/docs/features/toolbar/) export button | | `toolbarRedo` | `Redo` | [Toolbar](https://coreui.io/data-grid/angular/docs/features/toolbar/) redo button | | `toolbarUndo` | `Undo` | [Toolbar](https://coreui.io/data-grid/angular/docs/features/toolbar/) undo button | | `undoneAnnouncement` | `Change undone` | [Undo & redo](https://coreui.io/data-grid/angular/docs/features/history/) ARIA live announcement | | `unpin` | `Unpin` | Column menu | The defaults are exported as `DEFAULT_LABELS` from `@coreui/angular-data-grid` if you want to extend rather than replace them. --- # Angular Data Grid Accessibility > How CoreUI Data Grid for Angular supports keyboard interaction, ARIA roles and screen-reader announcements, and where the current limits are. The Data Grid ships accessible controls for its interactive chrome and exposes every string for translation. This page documents what's supported today and what's still on the [roadmap](https://coreui.io/data-grid/angular/docs/resources/roadmap/). ## Grid keyboard navigation With [`cellNavigation`](https://coreui.io/data-grid/angular/docs/features/keyboard-navigation/) on, the grid implements the full ARIA [grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/): `role="grid"`, `aria-colcount`/`aria-colindex` alongside the absolute `aria-rowcount`/`aria-rowindex`, a single tab stop with a roving-tabindex active cell, and arrow-key movement across header and data cells. The option is opt-in — claiming `role="grid"` without the full keyboard contract would be worse than the native table semantics the grid keeps by default. ## Column menu The [column menu](https://coreui.io/data-grid/angular/docs/columns/menu/) follows the ARIA menu pattern: - **Arrow keys** move between items; **Home/End** jump to the first/last item. - **Escape** closes the menu and restores focus to the ⋮ trigger. - **Move left / Move right** give a keyboard-accessible alternative to drag-and-drop [column reordering](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/). All menu labels come from the [`labels`](https://coreui.io/data-grid/angular/docs/customization/localization/) input, so the menu is fully translatable. ## Selection The [selection](https://coreui.io/data-grid/angular/docs/features/row-selection/) checkboxes are real form controls with accessible labels (`selectRow`, `selectAllRows`) — reachable and toggleable by keyboard. ## Server-side loading In [server-side mode](https://coreui.io/data-grid/angular/docs/features/server-side-data/) the grid sets `aria-busy` while a request is in flight and exposes the `loading` label, so assistive tech announces the pending state. ## Live announcements Result counts are announced through an ARIA live region using the `resultsAnnouncement` label (`{count} results`) as filters change. ## Current limits `cellNavigation` (and the [inline editing](https://coreui.io/data-grid/angular/docs/features/editing/) built on it) is opt-in in this release. Screen-reader-verified defaults — flipping `role="grid"` on out of the box — are a separate decision planned after NVDA and VoiceOver testing. --- # Angular Data Grid Performance > How CoreUI Data Grid for Angular stays fast at 100,000 rows, the inputs that tune rendering, and when to move paging to your backend. The Data Grid is built to stay responsive on large datasets. This page explains how it does that and the levers you have when you need to tune it. ## Why it's fast - **Row virtualization.** Only the rows in the scroll viewport (plus a buffer) exist in the DOM — see [Virtualization](https://coreui.io/data-grid/angular/docs/features/virtualization/). A 100,000-row grid renders a few dozen `` elements, not 100,000. - **Cheap cell values.** Column [`formatter`](https://coreui.io/data-grid/angular/docs/columns/overview/) runs on the scroll hot path and returns a string, so it stays cheap. Reserve the `cDataGridCell` template (which stamps embedded views) for the columns that truly need interactive content. - **Debounced, race-safe fetches.** In [server-side mode](https://coreui.io/data-grid/angular/docs/features/server-side-data/), rapid state changes coalesce into one request and stale responses are dropped. ## Tuning levers | Lever | Effect | | --- | --- | | `rowHeight` | The estimated row height (default `44`). Set it close to your real height so the virtualizer sizes the scroll area accurately. | | `overscan` | Extra rows rendered above/below the viewport (default `10`). Raise it if you see blank rows during fast scrolling; lower it to shave DOM nodes. | | `formatter` vs cell template | Prefer `formatter` for values on the hot path; a `cDataGridCell` template stamps a view per visible cell. | ## When to go server-side Client-side mode keeps the whole dataset in memory. That's fine for tens of thousands of rows, but past what the browser can hold — or when the data lives in a database you don't want to ship whole — move sorting, filtering and paging to your API with [server-side data](https://coreui.io/data-grid/angular/docs/features/server-side-data/). The grid then holds only the current page. ## Rules of thumb - A few thousand to ~100k rows in the browser → client-side with virtualization. - Beyond that, or data behind an API → server-side mode. - Keep `formatter` pure and cheap; it runs for every visible cell on every render. --- # Angular Data Grid Options > Full reference of CoreUI Data Grid for Angular inputs — columns, data, features and behavior. Every option is an input on ``. Inputs are reactive — change a bound value and the grid re-renders; there is no imperative `update()` call. Feature inputs accept `true` for the defaults or an object to configure the feature — see each feature's page for its keys: [sorting](https://coreui.io/data-grid/angular/docs/features/sorting/), [filtering](https://coreui.io/data-grid/angular/docs/features/filtering/), [row selection](https://coreui.io/data-grid/angular/docs/features/row-selection/), [pagination](https://coreui.io/data-grid/angular/docs/features/pagination/), [server-side data](https://coreui.io/data-grid/angular/docs/features/server-side-data/), [column sizing](https://coreui.io/data-grid/angular/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/angular/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/angular/docs/columns/ordering-visibility/) and the [column menu](https://coreui.io/data-grid/angular/docs/columns/menu/). Column definitions are documented in [Columns](https://coreui.io/data-grid/angular/docs/api/columns/). ## Content templates Where the vanilla grid takes `slots`, `filter` and `render` options, the Angular grid takes `ng-template` content children: | Template | Context | Description | | --- | --- | --- | | `` | `$implicit: item`, `index`, `value` | Custom cell content for the column `key`. See [Columns overview](https://coreui.io/data-grid/angular/docs/columns/overview/). | | `` | `$implicit: column`, `table`, `labels` | Custom filter UI for the column `key`. See [Filtering](https://coreui.io/data-grid/angular/docs/features/filtering/). | | `` | `$implicit: table`, `labels` | Replaces a built-in chrome module. See [Slots](https://coreui.io/data-grid/angular/docs/features/slots/). | Import the matching directive (`DataGridCellDirective`, `DataGridColumnFilterDirective`, `DataGridSlotDirective`) alongside `DataGridComponent` to use a template. --- # Angular Data Grid Column API > Full reference of CoreUI Data Grid for Angular column definition keys — labels, sorting, filtering, formatting and custom rendering. Each entry in the [`columns`](https://coreui.io/data-grid/angular/docs/api/options/) array describes one column. `key` is the only required field. See [Columns overview](https://coreui.io/data-grid/angular/docs/columns/overview/) for a guided tour. | Key | Type | Description | | --- | --- | --- | | `key` | `string` | Property name in the item object (also the column id). | | `label` | `string` | Header label; falls back to `key`. | | `editable` | `boolean \| { type?, min?, max?, step?, options? }` | Opts the column into [inline editing](https://coreui.io/data-grid/angular/docs/features/editing/) with a built-in `text`, `number` or `select` editor. | | `editorPopup` | `boolean` | Renders the `cDataGridCellEditor` template in an overlay anchored to the cell instead of inline — for rich editors whose UI extends beyond the cell. | | `editValidate` | `(value, item) => true \| string` | Gates the commit — a returned message blocks it and marks the editor invalid. | | `sortable` | `boolean` | Set `false` to disable sorting for this column. | | `filterable` | `boolean` | Set `false` to remove the column's filter button. | | `filterFn` | `(value, filterValue, item) => boolean` | Custom predicate replacing the default case-insensitive contains. | | `filterType` | `'text' \| 'number' \| 'date' \| 'select'` | Operator set for the built-in [filter menu](https://coreui.io/data-grid/angular/docs/features/filtering/); `select` renders the faceted set filter. Defaults to `text`. | | `resizable` | `boolean` | Set `false` to drop the resize handle for this column (when `columnSizing` is on). | | `movable` | `boolean` | Set `false` to exclude the column from drag-and-drop reordering. | | `hideable` | `boolean` | Set `false` to prevent hiding the column. | | `formatter` | `(value, item) => string` | Formats the cell value — cheap, stays on the scroll hot path. | | `width` | `number` | Initial column width in pixels - seeds `columnSizing` and drives the layout. | | `style` | `object` | Inline styles for the header cell (cosmetic; e.g. percentage widths are visual-only). | `formatter` runs on the scroll hot path and its output is used for [CSV export](https://coreui.io/data-grid/angular/docs/features/csv-export/). Where the vanilla column definition takes `filter` and `render` functions, the Angular grid uses content templates instead: a custom filter UI is an `` (see [Filtering](https://coreui.io/data-grid/angular/docs/features/filtering/)) and rich cell content is an `` (see [Columns overview](https://coreui.io/data-grid/angular/docs/columns/overview/)) — the cell template is never exported. Use `formatter` **or** a cell template per column. --- # Angular Data Grid Events > CoreUI Data Grid for Angular outputs — each carrying structured state. All grid events are component outputs carrying structured state. Listen with an event binding — `(sortingChange)="onSortingChange($event)"`. Every payload interface (e.g. `DataGridSortingChangeEvent`) is exported from `@coreui/angular-data-grid`. | Output | Payload | | --- | --- | | `sortingChange` | `{ sorting: SortingState }` | | `filterChange` | `{ columnFilters: ColumnFiltersState, globalFilter: string }` | | `selectionChange` | `{ rowSelection: RowSelectionState, selectedItems: DataGridItem[] }` | | `paginationChange` | `{ pagination: PaginationState }` | | `editStart` | `{ item: object, columnId: string }` | | `editCommit` | `{ item, columnId, value, previousValue }` — the grid never mutates `items`; apply the change yourself. [Undo/redo](https://coreui.io/data-grid/angular/docs/features/history/) re-emits it with the values swapped | | `editCancel` | `{ item: object, columnId: string }` | | `sizingChange` | `{ columnSizing: ColumnSizingState }` | | `pinningChange` | `{ columnPinning: ColumnPinningState }` | | `orderChange` | `{ columnOrder: ColumnOrderState }` | | `visibilityChange` | `{ columnVisibility: VisibilityState }` | | `dataLoad` | `{ items: DataGridItem[], totalRows: number }` (server-side mode) | | `dataError` | `{ error: unknown }` (server-side mode) | --- # Angular Data Grid Methods > CoreUI Data Grid for Angular component members and helpers — the headless table getter, CSV export functions and default labels. Grab the component with `viewChild(DataGridComponent)` (or a template reference variable) to reach its members; the CSV helpers are standalone functions. | Member | Description | | --- | --- | | `table` (component property) | The underlying headless table instance — the [headless escape hatch](https://coreui.io/data-grid/angular/docs/api/headless/) for building custom UI. | | `exportCsv(table, options?)` | Returns the rows as an RFC-4180 CSV string. Options: `scope`, `delimiter`, `bom`, `sanitize`. See [CSV export](https://coreui.io/data-grid/angular/docs/features/csv-export/). | | `downloadCsv(table, options?)` | Downloads the CSV as a file. Same options plus `filename`. | | `DEFAULT_LABELS` | The default UI strings, for extending rather than replacing. See [Localization](https://coreui.io/data-grid/angular/docs/customization/localization/). | ```ts import { Component, viewChild } from '@angular/core' import { DataGridComponent, downloadCsv } from '@coreui/angular-data-grid' @Component({ /* … */ }) export class UsersComponent { private readonly grid = viewChild.required(DataGridComponent) export() { downloadCsv(this.grid().table, { filename: 'users.csv' }) } } ``` Where the vanilla grid has imperative methods, the Angular grid leans on the framework instead: - **`update(options)`** — change the bound inputs; the grid re-renders reactively. - **`dispose()`** — the component cleans up when Angular destroys it. - **`getSelectedItems()`** — read `selectedItems` from the [`selectionChange`](https://coreui.io/data-grid/angular/docs/api/events/) output, or call `grid.table.getSelectedRowModel().rows.map((row) => row.original)`. --- # Angular Data Grid Headless Table > Drop down to the underlying headless table instance to build custom Angular Data Grid UI and drive state imperatively. The Data Grid is a thin, styled layer over a headless table engine. When the built-in chrome isn't enough, turn it off and drive the grid from your own UI through the component's `table` property — the same instance the grid renders from. ```ts import { Component, viewChild } from '@angular/core' import { DataGridComponent } from '@coreui/angular-data-grid' @Component({ selector: 'app-users', imports: [DataGridComponent], template: ` ` }) export class UsersComponent { private readonly grid = viewChild.required(DataGridComponent) // Drive state imperatively through the underlying table instance: example() { this.grid().table.setPageIndex(3) this.grid().table.getFilteredRowModel() this.grid().table.setColumnPinning({ left: ['name'] }) this.grid().table.getState().sorting } } ``` ## When to reach for it - **Custom chrome.** Build your own toolbar, pager or column chooser and wire it to `grid.table.*`. The [slot templates](https://coreui.io/data-grid/angular/docs/features/slots/) hand you the same `table` in their template context — prefer a slot when you only need to replace one module. - **Reading state.** `grid.table.getState()` exposes sorting, filters, selection, pagination, pinning, order and visibility as structured state. - **Imperative actions.** Set the page, toggle a column, change pinning or apply a filter without waiting for user interaction. ## Notes Everything the built-in UI does routes through this same table, so your imperative calls and the built-in controls stay in sync. Grid [outputs](https://coreui.io/data-grid/angular/docs/api/events/) fire for headless-driven changes too. --- # Angular Data Grid Roadmap > Features planned for future CoreUI Data Grid releases — inline editing, grouping and aggregation, tree data, full grid keyboard navigation and dynamic row heights. The Data Grid is under active development. These capabilities are planned for future releases; the pages in this documentation cover what ships today. ## Shipped - **[Inline editing](https://coreui.io/data-grid/angular/docs/features/editing/)** — edit cell values in place, with built-in editors and a popup contract for rich ones. - **[Full keyboard navigation](https://coreui.io/data-grid/angular/docs/features/keyboard-navigation/)** — `role="grid"` cell-by-cell arrow-key movement, extending the [accessible chrome](https://coreui.io/data-grid/angular/docs/guides/accessibility/). ## Planned - **Grouping & aggregation** — group rows and compute totals per group. - **Tree data** — hierarchical, expandable rows. - **Dynamic row heights** — per-row heights in the virtualizer, beyond today's fixed `rowHeight`. Have a request or want to track progress? Open an issue on the [CoreUI Data Grid repository](https://github.com/coreui/coreui-data-grid).