@tanstack/angular-table 9.0.0-alpha.47 → 9.0.0-alpha.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/README.md +10 -0
- package/dist/fesm2022/tanstack-angular-table.mjs +18 -18
- package/package.json +8 -6
- package/skills/angular/angular-rendering-directives/SKILL.md +415 -0
- package/skills/angular/angular-rendering-directives/references/content-shapes.md +142 -0
- package/skills/angular/angular-rendering-directives/references/create-table-hook-registries.md +89 -0
- package/skills/angular/angular-rendering-directives/references/di-tokens.md +171 -0
- package/skills/angular/angular-rendering-directives/references/flex-render-component-options.md +64 -0
- package/skills/angular/client-to-server/SKILL.md +467 -0
- package/skills/angular/compose-with-tanstack-query/SKILL.md +482 -0
- package/skills/angular/compose-with-tanstack-store/SKILL.md +396 -0
- package/skills/angular/compose-with-tanstack-virtual/SKILL.md +400 -0
- package/skills/angular/getting-started/SKILL.md +496 -0
- package/skills/angular/getting-started/references/feature-row-model-mapping.md +48 -0
- package/skills/angular/migrate-v8-to-v9/SKILL.md +415 -0
- package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +261 -0
- package/skills/angular/production-readiness/SKILL.md +468 -0
- package/skills/angular/table-state/SKILL.md +425 -0
- package/skills/angular/table-state/references/external-atoms-and-app-hook.md +152 -0
package/README.md
CHANGED
|
@@ -49,6 +49,16 @@ A headless table library for building powerful datagrids with full control over
|
|
|
49
49
|
|
|
50
50
|
### <a href="https://tanstack.com/table">Read the Docs →</a>
|
|
51
51
|
|
|
52
|
+
## Using an AI Coding Agent?
|
|
53
|
+
|
|
54
|
+
TanStack Table ships [TanStack Intent](https://github.com/TanStack/intent) skills inside each adapter package. After installing the library, run:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npx @tanstack/intent@latest install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
to add skill-loading guidance for your agent (Claude Code, Cursor, Copilot, etc.). The same CLI also exposes `intent list` to browse available skills and `intent load <skill>` to print one for inspection. Skills version with the library — your agent gets guidance that matches the version of `@tanstack/<framework>-table` you installed. Only available for v9 and above.
|
|
61
|
+
|
|
52
62
|
## Get Involved
|
|
53
63
|
|
|
54
64
|
- We welcome issues and pull requests!
|
package/dist/README.md
CHANGED
|
@@ -49,6 +49,16 @@ A headless table library for building powerful datagrids with full control over
|
|
|
49
49
|
|
|
50
50
|
### <a href="https://tanstack.com/table">Read the Docs →</a>
|
|
51
51
|
|
|
52
|
+
## Using an AI Coding Agent?
|
|
53
|
+
|
|
54
|
+
TanStack Table ships [TanStack Intent](https://github.com/TanStack/intent) skills inside each adapter package. After installing the library, run:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npx @tanstack/intent@latest install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
to add skill-loading guidance for your agent (Claude Code, Cursor, Copilot, etc.). The same CLI also exposes `intent list` to browse available skills and `intent load <skill>` to print one for inspection. Skills version with the library — your agent gets guidance that matches the version of `@tanstack/<framework>-table` you installed. Only available for v9 and above.
|
|
61
|
+
|
|
52
62
|
## Get Involved
|
|
53
63
|
|
|
54
64
|
- We welcome issues and pull requests!
|
|
@@ -55,15 +55,15 @@ class TanStackTableCell {
|
|
|
55
55
|
* Provided as a required signal input so DI consumers always read the latest value.
|
|
56
56
|
*/
|
|
57
57
|
cell = input.required({ ...(ngDevMode ? { debugName: "cell" } : /* istanbul ignore next */ {}), alias: 'tanStackTableCell' });
|
|
58
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
59
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.
|
|
58
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTableCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
59
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: TanStackTableCell, isStandalone: true, selector: "[tanStackTableCell]", inputs: { cell: { classPropertyName: "cell", publicName: "tanStackTableCell", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
60
60
|
{
|
|
61
61
|
provide: TanStackTableCellToken,
|
|
62
62
|
useFactory: () => inject(TanStackTableCell).cell,
|
|
63
63
|
},
|
|
64
64
|
], exportAs: ["cell"], ngImport: i0 });
|
|
65
65
|
}
|
|
66
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
66
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTableCell, decorators: [{
|
|
67
67
|
type: Directive,
|
|
68
68
|
args: [{
|
|
69
69
|
selector: '[tanStackTableCell]',
|
|
@@ -132,15 +132,15 @@ class TanStackTableHeader {
|
|
|
132
132
|
* Provided as a required signal input so DI consumers always read the latest value.
|
|
133
133
|
*/
|
|
134
134
|
header = input.required({ ...(ngDevMode ? { debugName: "header" } : /* istanbul ignore next */ {}), alias: 'tanStackTableHeader' });
|
|
135
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
136
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.
|
|
135
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTableHeader, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
136
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: TanStackTableHeader, isStandalone: true, selector: "[tanStackTableHeader]", inputs: { header: { classPropertyName: "header", publicName: "tanStackTableHeader", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
137
137
|
{
|
|
138
138
|
provide: TanStackTableHeaderToken,
|
|
139
139
|
useFactory: () => inject(TanStackTableHeader).header,
|
|
140
140
|
},
|
|
141
141
|
], exportAs: ["header"], ngImport: i0 });
|
|
142
142
|
}
|
|
143
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
143
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTableHeader, decorators: [{
|
|
144
144
|
type: Directive,
|
|
145
145
|
args: [{
|
|
146
146
|
selector: '[tanStackTableHeader]',
|
|
@@ -211,15 +211,15 @@ class TanStackTable {
|
|
|
211
211
|
* Provided as a required signal input so DI consumers always read the latest value.
|
|
212
212
|
*/
|
|
213
213
|
table = input.required({ ...(ngDevMode ? { debugName: "table" } : /* istanbul ignore next */ {}), alias: 'tanStackTable' });
|
|
214
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
215
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.
|
|
214
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
215
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: TanStackTable, isStandalone: true, selector: "[tanStackTable]", inputs: { table: { classPropertyName: "table", publicName: "tanStackTable", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
216
216
|
{
|
|
217
217
|
provide: TanStackTableToken,
|
|
218
218
|
useFactory: () => inject(TanStackTable).table,
|
|
219
219
|
},
|
|
220
220
|
], exportAs: ["table"], ngImport: i0 });
|
|
221
221
|
}
|
|
222
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
222
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTable, decorators: [{
|
|
223
223
|
type: Directive,
|
|
224
224
|
args: [{
|
|
225
225
|
selector: '[tanStackTable]',
|
|
@@ -378,10 +378,10 @@ class FlexRenderComponentFactory {
|
|
|
378
378
|
view.setOutputs(outputs);
|
|
379
379
|
return view;
|
|
380
380
|
}
|
|
381
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
382
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
381
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderComponentFactory, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
382
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderComponentFactory });
|
|
383
383
|
}
|
|
384
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
384
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderComponentFactory, decorators: [{
|
|
385
385
|
type: Injectable
|
|
386
386
|
}], ctorParameters: () => [{ type: i0.ViewContainerRef }] });
|
|
387
387
|
/**
|
|
@@ -984,10 +984,10 @@ class FlexRenderCell {
|
|
|
984
984
|
renderer.destroy();
|
|
985
985
|
});
|
|
986
986
|
}
|
|
987
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
988
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.
|
|
987
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
988
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: FlexRenderCell, isStandalone: true, selector: "ng-template[flexRenderCell], ng-template[flexRenderFooter], ng-template[flexRenderHeader]", inputs: { cell: { classPropertyName: "cell", publicName: "flexRenderCell", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "flexRenderHeader", isSignal: true, isRequired: false, transformFunction: null }, footer: { classPropertyName: "footer", publicName: "flexRenderFooter", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
989
989
|
}
|
|
990
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
990
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderCell, decorators: [{
|
|
991
991
|
type: Directive,
|
|
992
992
|
args: [{
|
|
993
993
|
selector: 'ng-template[flexRenderCell], ng-template[flexRenderFooter], ng-template[flexRenderHeader]',
|
|
@@ -1061,10 +1061,10 @@ class FlexRenderDirective {
|
|
|
1061
1061
|
renderer.destroy();
|
|
1062
1062
|
});
|
|
1063
1063
|
}
|
|
1064
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1065
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.
|
|
1064
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1065
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: FlexRenderDirective, isStandalone: true, selector: "ng-template[flexRender]", inputs: { content: { classPropertyName: "content", publicName: "flexRender", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "flexRenderProps", isSignal: true, isRequired: false, transformFunction: null }, injector: { classPropertyName: "injector", publicName: "flexRenderInjector", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
1066
1066
|
}
|
|
1067
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1067
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderDirective, decorators: [{
|
|
1068
1068
|
type: Directive,
|
|
1069
1069
|
args: [{
|
|
1070
1070
|
selector: 'ng-template[flexRender]',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/angular-table",
|
|
3
|
-
"version": "9.0.0-alpha.
|
|
3
|
+
"version": "9.0.0-alpha.49",
|
|
4
4
|
"description": "Headless UI for building powerful tables & datagrids for Angular.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"angular",
|
|
19
19
|
"table",
|
|
20
20
|
"angular-table",
|
|
21
|
-
"datagrid"
|
|
21
|
+
"datagrid",
|
|
22
|
+
"tanstack-intent"
|
|
22
23
|
],
|
|
23
24
|
"type": "module",
|
|
24
25
|
"module": "dist/fesm2022/tanstack-angular-table.mjs",
|
|
@@ -46,18 +47,19 @@
|
|
|
46
47
|
"files": [
|
|
47
48
|
"dist",
|
|
48
49
|
"src",
|
|
49
|
-
"static-functions"
|
|
50
|
+
"static-functions",
|
|
51
|
+
"skills"
|
|
50
52
|
],
|
|
51
53
|
"dependencies": {
|
|
52
54
|
"@tanstack/angular-store": "^0.11.0",
|
|
53
55
|
"tslib": "^2.8.1",
|
|
54
|
-
"@tanstack/table-core": "9.0.0-alpha.
|
|
56
|
+
"@tanstack/table-core": "9.0.0-alpha.49"
|
|
55
57
|
},
|
|
56
58
|
"devDependencies": {
|
|
57
59
|
"@analogjs/vite-plugin-angular": "^2.5.1",
|
|
58
60
|
"@analogjs/vitest-angular": "^2.5.1",
|
|
59
|
-
"@angular/core": "^21.2.
|
|
60
|
-
"@angular/platform-browser": "^21.2.
|
|
61
|
+
"@angular/core": "^21.2.13",
|
|
62
|
+
"@angular/platform-browser": "^21.2.13",
|
|
61
63
|
"ng-packagr": "^21.2.3",
|
|
62
64
|
"typescript": "6.0.3"
|
|
63
65
|
},
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular/angular-rendering-directives
|
|
3
|
+
description: >
|
|
4
|
+
Angular's structural-directive rendering pipeline for TanStack Table v9. Covers `FlexRender`
|
|
5
|
+
(`*flexRender`), the shorthand directives `*flexRenderCell` / `*flexRenderHeader` /
|
|
6
|
+
`*flexRenderFooter`, the `flexRenderComponent(Component, { inputs, outputs, bindings, directives, injector })`
|
|
7
|
+
wrapper, DI tokens `TanStackTableToken` / `TanStackTableHeaderToken` / `TanStackTableCellToken`
|
|
8
|
+
with their `injectTableContext()` / `injectTableHeaderContext()` / `injectTableCellContext()`
|
|
9
|
+
helpers, the `[tanStackTable]` / `[tanStackTableHeader]` / `[tanStackTableCell]` host directives,
|
|
10
|
+
`injectFlexRenderContext()`, automatic token injection inside `*flexRender`, and the four
|
|
11
|
+
content shapes (primitive, `TemplateRef`, component type, `flexRenderComponent` wrapper).
|
|
12
|
+
type: framework
|
|
13
|
+
library: tanstack-table
|
|
14
|
+
framework: angular
|
|
15
|
+
library_version: '9.0.0-alpha.48'
|
|
16
|
+
requires:
|
|
17
|
+
- angular/table-state
|
|
18
|
+
- column-definitions
|
|
19
|
+
sources:
|
|
20
|
+
- TanStack/table:docs/framework/angular/guide/rendering.md
|
|
21
|
+
- TanStack/table:packages/angular-table/src/flexRender.ts
|
|
22
|
+
- TanStack/table:packages/angular-table/src/helpers/flexRenderCell.ts
|
|
23
|
+
- TanStack/table:packages/angular-table/src/helpers/cell.ts
|
|
24
|
+
- TanStack/table:packages/angular-table/src/helpers/header.ts
|
|
25
|
+
- TanStack/table:packages/angular-table/src/helpers/table.ts
|
|
26
|
+
- TanStack/table:packages/angular-table/src/flex-render/flexRenderComponent.ts
|
|
27
|
+
- TanStack/table:packages/angular-table/src/flex-render/context.ts
|
|
28
|
+
- TanStack/table:packages/angular-table/src/flex-render/renderer.ts
|
|
29
|
+
- TanStack/table:examples/angular/basic-inject-table/
|
|
30
|
+
- TanStack/table:examples/angular/editable/
|
|
31
|
+
- TanStack/table:examples/angular/row-selection-signal/
|
|
32
|
+
- TanStack/table:examples/angular/composable-tables/
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# Angular Rendering Directives (v9)
|
|
36
|
+
|
|
37
|
+
> **This skill is Angular-only.** Agents trained on React/Vue copy the wrong
|
|
38
|
+
> pattern here — there is no `flexRender(cell.column.columnDef.cell, cell.getContext())`
|
|
39
|
+
> call expression in Angular. Rendering is **structural-directive based**:
|
|
40
|
+
> `*flexRender`, `*flexRenderCell`, `*flexRenderHeader`, `*flexRenderFooter`.
|
|
41
|
+
>
|
|
42
|
+
> Read `tanstack-table/angular/table-state` first.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 1. Setup: the single import — `FlexRender`
|
|
47
|
+
|
|
48
|
+
`FlexRender` is exported as a _tuple_ of two directives so a single import gets
|
|
49
|
+
you everything:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { FlexRender } from '@tanstack/angular-table'
|
|
53
|
+
|
|
54
|
+
@Component({
|
|
55
|
+
imports: [FlexRender], // imports BOTH FlexRenderDirective + FlexRenderCell
|
|
56
|
+
templateUrl: './app.html',
|
|
57
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
58
|
+
})
|
|
59
|
+
export class App {}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`FlexRender` ≡ `[FlexRenderDirective, FlexRenderCell] as const`. Prefer this over
|
|
63
|
+
importing the individual directives — it survives future additions.
|
|
64
|
+
|
|
65
|
+
Tokens / context directives (`TanStackTable`, `TanStackTableHeader`,
|
|
66
|
+
`TanStackTableCell`) are separate imports — add them only when you use them.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 2. The shorthand directives — your default
|
|
71
|
+
|
|
72
|
+
For standard header / cell / footer rendering, **prefer the shorthand directives**.
|
|
73
|
+
They resolve `columnDef.cell` / `columnDef.header` / `columnDef.footer` and the
|
|
74
|
+
right context for you, so you don't write `props:` by hand.
|
|
75
|
+
|
|
76
|
+
| Directive | Input | Resolves |
|
|
77
|
+
| --------------------------------------- | -------- | ------------------------------------------ |
|
|
78
|
+
| `*flexRenderCell="cell; let value"` | `Cell` | `columnDef.cell` + `cell.getContext()` |
|
|
79
|
+
| `*flexRenderHeader="header; let value"` | `Header` | `columnDef.header` + `header.getContext()` |
|
|
80
|
+
| `*flexRenderFooter="footer; let value"` | `Header` | `columnDef.footer` + `header.getContext()` |
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<table>
|
|
84
|
+
<thead>
|
|
85
|
+
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
|
|
86
|
+
<tr>
|
|
87
|
+
@for (header of headerGroup.headers; track header.id) {
|
|
88
|
+
<th>
|
|
89
|
+
@if (!header.isPlaceholder) {
|
|
90
|
+
<ng-container *flexRenderHeader="header; let value">
|
|
91
|
+
{{ value }}
|
|
92
|
+
</ng-container>
|
|
93
|
+
}
|
|
94
|
+
</th>
|
|
95
|
+
}
|
|
96
|
+
</tr>
|
|
97
|
+
}
|
|
98
|
+
</thead>
|
|
99
|
+
|
|
100
|
+
<tbody>
|
|
101
|
+
@for (row of table.getRowModel().rows; track row.id) {
|
|
102
|
+
<tr>
|
|
103
|
+
@for (cell of row.getVisibleCells(); track cell.id) {
|
|
104
|
+
<td>
|
|
105
|
+
<ng-container *flexRenderCell="cell; let value">
|
|
106
|
+
{{ value }}
|
|
107
|
+
</ng-container>
|
|
108
|
+
</td>
|
|
109
|
+
}
|
|
110
|
+
</tr>
|
|
111
|
+
}
|
|
112
|
+
</tbody>
|
|
113
|
+
|
|
114
|
+
<tfoot>
|
|
115
|
+
@for (footerGroup of table.getFooterGroups(); track footerGroup.id) {
|
|
116
|
+
<tr>
|
|
117
|
+
@for (footer of footerGroup.headers; track footer.id) {
|
|
118
|
+
<th *flexRenderFooter="footer; let value">{{ value }}</th>
|
|
119
|
+
}
|
|
120
|
+
</tr>
|
|
121
|
+
}
|
|
122
|
+
</tfoot>
|
|
123
|
+
</table>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The shorthand selector matches on `ng-template[flexRenderCell]`, so applying it
|
|
127
|
+
to a `<td>` host (`<td *flexRenderCell="cell; let value">`) desugars to an
|
|
128
|
+
`<ng-template>` Angular renders inside the `<td>`. Both forms are valid.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 3. The four content shapes a column def can produce
|
|
133
|
+
|
|
134
|
+
`columnDef.cell` / `header` / `footer` can return any of these — `FlexRender`
|
|
135
|
+
dispatches on the shape:
|
|
136
|
+
|
|
137
|
+
| Returned value | Renderer behavior |
|
|
138
|
+
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
139
|
+
| **Primitive** (`string`, `number`, `null`, `undefined`) | implicit `$implicit` value via `createEmbeddedView`. |
|
|
140
|
+
| **`TemplateRef`** | render context passed as `$implicit`. |
|
|
141
|
+
| **Component type** (`Type<T>`) | `createComponent`; context properties forwarded via `setInput(...)` to matching `input()` slots. |
|
|
142
|
+
| **`flexRenderComponent(Component, options)`** | `createComponent` with explicit `inputs`, `outputs`, `bindings`, `directives`, `injector`. |
|
|
143
|
+
|
|
144
|
+
Render functions run inside `runInInjectionContext`, so you can call `inject()`,
|
|
145
|
+
read signals, and access DI tokens from within `cell: () => …`.
|
|
146
|
+
|
|
147
|
+
See [`references/content-shapes.md`](references/content-shapes.md) for full
|
|
148
|
+
worked examples of each shape (primitives, `TemplateRef` via `viewChild`,
|
|
149
|
+
returning component classes with `input()` slots).
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 4. The primitive cell pattern
|
|
154
|
+
|
|
155
|
+
The 90% case — return a value, render it.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const columns: Array<ColumnDef<typeof _features, Person>> = [
|
|
159
|
+
{
|
|
160
|
+
accessorKey: 'firstName',
|
|
161
|
+
header: 'First name',
|
|
162
|
+
cell: (info) => info.getValue(),
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```html
|
|
168
|
+
<td *flexRenderCell="cell; let value">{{ value }}</td>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 5. Component cells: pass-through vs `flexRenderComponent`
|
|
174
|
+
|
|
175
|
+
### Pass-through — return the component class
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
@Component({
|
|
179
|
+
selector: 'app-select-all',
|
|
180
|
+
template: `
|
|
181
|
+
<input
|
|
182
|
+
type="checkbox"
|
|
183
|
+
[checked]="table().getIsAllRowsSelected()"
|
|
184
|
+
(change)="table().toggleAllRowsSelected()"
|
|
185
|
+
/>
|
|
186
|
+
`,
|
|
187
|
+
})
|
|
188
|
+
export class SelectAllComponent<T> {
|
|
189
|
+
readonly table = input.required<Table<any, T>>()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const columns: Array<ColumnDef<typeof _features, Person>> = [
|
|
193
|
+
{
|
|
194
|
+
id: 'select',
|
|
195
|
+
header: () => SelectAllComponent,
|
|
196
|
+
cell: () => RowSelectComponent,
|
|
197
|
+
},
|
|
198
|
+
]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The renderer calls `setInput(name, value)` for every property on the render
|
|
202
|
+
context — declare `input()` / `input.required()` for the names you need.
|
|
203
|
+
|
|
204
|
+
### `flexRenderComponent(Component, options)` — explicit wiring
|
|
205
|
+
|
|
206
|
+
Reach for the wrapper when you need custom inputs not on the context, output
|
|
207
|
+
callbacks, a custom injector, or Angular v20+ `bindings` / `directives`:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { flexRenderComponent } from '@tanstack/angular-table'
|
|
211
|
+
|
|
212
|
+
cell: ({ getValue, row, column, table }) =>
|
|
213
|
+
flexRenderComponent(EditableCell, {
|
|
214
|
+
inputs: { value: getValue() },
|
|
215
|
+
outputs: {
|
|
216
|
+
change: (value) =>
|
|
217
|
+
table.options.meta?.updateData(row.index, column.id, value),
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Full option semantics (how `inputs` diff, how `outputs` resolve `OutputEmitterRef`,
|
|
223
|
+
when to use `bindings` vs `inputs`, and the v20+ `inputBinding` / `twoWayBinding`
|
|
224
|
+
API) → [`references/flex-render-component-options.md`](references/flex-render-component-options.md).
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 6. DI tokens & context injection — the prop-drill killer
|
|
229
|
+
|
|
230
|
+
`FlexRender` automatically provides DI tokens for the render context to any
|
|
231
|
+
component rendered through `*flexRender` / `*flexRenderCell` / etc. The renderer
|
|
232
|
+
inspects the props object — if it has `table`, `header`, or `cell`, it
|
|
233
|
+
provides the matching token in the child injector.
|
|
234
|
+
|
|
235
|
+
| Helper | Returns |
|
|
236
|
+
| ------------------------------------------- | --------------------------------- |
|
|
237
|
+
| `injectFlexRenderContext<T>()` | `T` (full props proxy) |
|
|
238
|
+
| `injectTableContext<TData>()` | `Signal<Table<TFeatures, TData>>` |
|
|
239
|
+
| `injectTableHeaderContext<TValue, TData>()` | `Signal<Header<...>>` |
|
|
240
|
+
| `injectTableCellContext<TValue, TData>()` | `Signal<Cell<...>>` |
|
|
241
|
+
|
|
242
|
+
### Pattern A — inside the rendered component itself (auto-provided)
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
@Component({ template: `{{ cell().getValue() }}` })
|
|
246
|
+
export class TextCell {
|
|
247
|
+
readonly cell = injectTableCellContext<string>()
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Pattern B — for descendants outside the FlexRender tree
|
|
252
|
+
|
|
253
|
+
Apply `[tanStackTable]`, `[tanStackTableHeader]`, or `[tanStackTableCell]` on
|
|
254
|
+
the host element to make the token available to children that aren't rendered
|
|
255
|
+
through `*flexRender*`:
|
|
256
|
+
|
|
257
|
+
```html
|
|
258
|
+
<td [tanStackTableCell]="cell">
|
|
259
|
+
<ng-container *flexRenderCell="cell; let value">{{ value }}</ng-container>
|
|
260
|
+
<app-cell-actions />
|
|
261
|
+
<!-- ↑ calls injectTableCellContext() — no input drilling -->
|
|
262
|
+
</td>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Full directive shapes, scoping rules, `*flexRender` long-form, custom
|
|
266
|
+
`flexRenderInjector:`, and `runInInjectionContext` behavior in column defs →
|
|
267
|
+
[`references/di-tokens.md`](references/di-tokens.md).
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 7. The `track` value matters
|
|
272
|
+
|
|
273
|
+
Angular `@for` requires `track` and TanStack Table gives you stable IDs. Use
|
|
274
|
+
them:
|
|
275
|
+
|
|
276
|
+
```html
|
|
277
|
+
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { ... } @for
|
|
278
|
+
(header of headerGroup.headers; track header.id) { ... } @for (row of
|
|
279
|
+
table.getRowModel().rows; track row.id) { ... } @for (cell of
|
|
280
|
+
row.getVisibleCells(); track cell.id) { ... } @for (footerGroup of
|
|
281
|
+
table.getFooterGroups(); track footerGroup.id) { ... }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`row.id` defaults to row index; provide `getRowId: (row) => row.id` in your
|
|
285
|
+
table options when you have a stable primary key (required for stable row
|
|
286
|
+
selection across re-fetches, see `compose-with-tanstack-query`).
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 8. `createTableHook` — registering components for whole-app use
|
|
291
|
+
|
|
292
|
+
`createTableHook(...)` accepts three optional component registries
|
|
293
|
+
(`tableComponents`, `cellComponents`, `headerComponents`) that become
|
|
294
|
+
type-safe members on `table.<Name>`, `cell.<Name>`, `header.<Name>`. Use them
|
|
295
|
+
to standardize cell/header/control components across an app while keeping
|
|
296
|
+
column defs short and typed.
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
export const { injectAppTable, createAppColumnHelper } = createTableHook({
|
|
300
|
+
_features: tableFeatures({ rowSortingFeature }),
|
|
301
|
+
_rowModels: {
|
|
302
|
+
/* … */
|
|
303
|
+
},
|
|
304
|
+
tableComponents: { PaginationControls, RowCount },
|
|
305
|
+
cellComponents: { TextCell, NumberCell, StatusCell },
|
|
306
|
+
headerComponents: { SortIndicator, ColumnFilter },
|
|
307
|
+
})
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Full registry semantics, `table.appCell(cell)` / `table.appHeader(header)`
|
|
311
|
+
narrowing, `NgComponentOutlet` usage, and worked examples →
|
|
312
|
+
[`references/create-table-hook-registries.md`](references/create-table-hook-registries.md).
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Failure modes
|
|
317
|
+
|
|
318
|
+
### 1. (CRITICAL) Copying React/Vue's `flexRender(fn, ctx)` call into Angular
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
// ❌ This is the React/Vue pattern — no such call expression exists in Angular
|
|
322
|
+
{{ flexRender(cell.column.columnDef.cell, cell.getContext()) }}
|
|
323
|
+
|
|
324
|
+
// ✅ Angular is directive-based
|
|
325
|
+
<td *flexRenderCell="cell; let value">{{ value }}</td>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
If an agent imports `flexRender` as a function from `@tanstack/angular-table`,
|
|
329
|
+
they've crossed wires. `FlexRender` in Angular is a **directive tuple constant**
|
|
330
|
+
used in `imports: [FlexRender]`.
|
|
331
|
+
|
|
332
|
+
### 2. (CRITICAL) Returning a component without `input()` slots
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
@Component({ template: `{{ row.original.firstName }}` })
|
|
336
|
+
export class NameCell {
|
|
337
|
+
row: any
|
|
338
|
+
} // ❌ no input() — `row` is never set
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The renderer calls `setInput('row', cell.row)`. Without `row = input<Row<any, any>>()`,
|
|
342
|
+
the property stays `undefined`. Either:
|
|
343
|
+
|
|
344
|
+
- declare `input()` / `input.required()` for the context properties you need, or
|
|
345
|
+
- use `injectFlexRenderContext()` / `injectTableCellContext()` for the full
|
|
346
|
+
proxy.
|
|
347
|
+
|
|
348
|
+
### 3. (CRITICAL) Reaching for `flexRenderComponent` for plain pass-through
|
|
349
|
+
|
|
350
|
+
If you don't need custom inputs/outputs/bindings/injector, **return the
|
|
351
|
+
component class directly** and let the renderer wire context inputs. Reserve
|
|
352
|
+
`flexRenderComponent(...)` for the cases that need explicit options.
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
// Plain pass-through — no wrapper needed
|
|
356
|
+
cell: () => TextCellComponent
|
|
357
|
+
|
|
358
|
+
// Wrapper needed — custom inputs/outputs
|
|
359
|
+
cell: ({ getValue }) =>
|
|
360
|
+
flexRenderComponent(EditableCell, {
|
|
361
|
+
inputs: { value: getValue() },
|
|
362
|
+
outputs: { change: (v) => {} },
|
|
363
|
+
})
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 4. (HIGH) Mixing `flexRenderComponent`'s `bindings` with `inputs` / `outputs`
|
|
367
|
+
|
|
368
|
+
`bindings` apply at component creation; `inputs`/`outputs` apply imperatively
|
|
369
|
+
after. Mixing them on the same property double-initializes the input or
|
|
370
|
+
conflicts the output. Pick one mechanism per component.
|
|
371
|
+
|
|
372
|
+
### 5. (HIGH) Forgetting `@if (!header.isPlaceholder)` for grouped/spanned headers
|
|
373
|
+
|
|
374
|
+
Grouped column defs produce placeholder headers in some header rows. Rendering
|
|
375
|
+
their `header` definition there is wrong. Always guard:
|
|
376
|
+
|
|
377
|
+
```html
|
|
378
|
+
@if (!header.isPlaceholder) {
|
|
379
|
+
<ng-container *flexRenderHeader="header; let value">{{ value }}</ng-container>
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 6. (MEDIUM) Drilling cell/header inputs through layers of wrapper components
|
|
384
|
+
|
|
385
|
+
The whole point of `[tanStackTableCell]` / `[tanStackTableHeader]` / `[tanStackTable]`
|
|
386
|
+
plus the inject helpers is to **stop input drilling**. If you find yourself
|
|
387
|
+
passing `cell` (or any context) through 3 component layers, replace it with
|
|
388
|
+
`injectTableCellContext()` at the leaf.
|
|
389
|
+
|
|
390
|
+
### 7. (MEDIUM) Assuming `injectTableCellContext()` works outside the FlexRender tree without the directive
|
|
391
|
+
|
|
392
|
+
The token is provided automatically inside `*flexRenderCell` / `*flexRender`
|
|
393
|
+
(when context has `cell`). For a sibling component in the same `<td>` that is
|
|
394
|
+
**not** rendered through `*flexRender*`, you must apply `[tanStackTableCell]="cell"`
|
|
395
|
+
on the host to make the token available. Same rule for header and table.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## References
|
|
400
|
+
|
|
401
|
+
- [Full content shape reference (primitive / TemplateRef / component class)](references/content-shapes.md)
|
|
402
|
+
- [`flexRenderComponent` options reference (inputs/outputs/bindings/injector)](references/flex-render-component-options.md)
|
|
403
|
+
- [DI tokens & host directives — patterns A & B, `*flexRender` long-form](references/di-tokens.md)
|
|
404
|
+
- [`createTableHook` component registries (table/cell/header)](references/create-table-hook-registries.md)
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## See also
|
|
409
|
+
|
|
410
|
+
- `tanstack-table/angular/table-state` — state model & `injectTable`
|
|
411
|
+
- `tanstack-table/angular/getting-started` — end-to-end first table
|
|
412
|
+
- `tanstack-table/core/column-definitions` — `cell` / `header` / `footer`
|
|
413
|
+
function signatures (framework-agnostic)
|
|
414
|
+
- `tanstack-table/angular/migrate-v8-to-v9` — `FlexRenderDirective` →
|
|
415
|
+
`*flexRender` shorthand evolution
|