@tanstack/angular-table 9.0.0-alpha.5 → 9.0.0-alpha.52
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 +127 -0
- package/dist/README.md +127 -0
- package/dist/fesm2022/tanstack-angular-table-static-functions.mjs +6 -0
- package/dist/fesm2022/tanstack-angular-table-static-functions.mjs.map +1 -0
- package/dist/fesm2022/tanstack-angular-table.mjs +1357 -242
- package/dist/fesm2022/tanstack-angular-table.mjs.map +1 -1
- package/dist/types/tanstack-angular-table-static-functions.d.ts +1 -0
- package/dist/types/tanstack-angular-table.d.ts +793 -0
- package/package.json +39 -19
- 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 +397 -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 +419 -0
- package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +261 -0
- package/skills/angular/production-readiness/SKILL.md +469 -0
- package/skills/angular/table-state/SKILL.md +429 -0
- package/skills/angular/table-state/references/external-atoms-and-app-hook.md +152 -0
- package/src/flex-render/context.ts +14 -0
- package/src/flex-render/flags.ts +34 -0
- package/src/flex-render/flexRenderComponent.ts +288 -0
- package/src/flex-render/flexRenderComponentFactory.ts +251 -0
- package/src/flex-render/renderer.ts +393 -0
- package/src/flex-render/view.ts +226 -0
- package/src/flexRender.ts +124 -0
- package/src/helpers/cell.ts +104 -0
- package/src/helpers/createTableHook.ts +499 -0
- package/src/helpers/flexRenderCell.ts +136 -0
- package/src/helpers/header.ts +99 -0
- package/src/helpers/table.ts +85 -0
- package/src/index.ts +23 -70
- package/src/injectTable.ts +206 -0
- package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
- package/src/reactivity.ts +147 -0
- package/static-functions/index.ts +1 -0
- package/static-functions/ng-package.json +6 -0
- package/dist/esm2022/flex-render.mjs +0 -150
- package/dist/esm2022/index.mjs +0 -48
- package/dist/esm2022/lazy-signal-initializer.mjs +0 -43
- package/dist/esm2022/proxy.mjs +0 -83
- package/dist/esm2022/tanstack-angular-table.mjs +0 -5
- package/dist/flex-render.d.ts +0 -31
- package/dist/index.d.ts +0 -5
- package/dist/lazy-signal-initializer.d.ts +0 -5
- package/dist/proxy.d.ts +0 -3
- package/src/__tests__/createAngularTable.test.ts +0 -95
- package/src/__tests__/flex-render.test.ts +0 -178
- package/src/__tests__/lazy-init.test.ts +0 -124
- package/src/__tests__/test-setup.ts +0 -12
- package/src/__tests__/test-utils.ts +0 -62
- package/src/flex-render.ts +0 -187
- package/src/proxy.ts +0 -97
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/angular-table",
|
|
3
|
-
"version": "9.0.0-alpha.
|
|
3
|
+
"version": "9.0.0-alpha.52",
|
|
4
4
|
"description": "Headless UI for building powerful tables & datagrids for Angular.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/TanStack/table.git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/table.git",
|
|
10
10
|
"directory": "packages/angular-table"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://tanstack.com/table",
|
|
@@ -18,16 +18,23 @@
|
|
|
18
18
|
"angular",
|
|
19
19
|
"table",
|
|
20
20
|
"angular-table",
|
|
21
|
-
"datagrid"
|
|
21
|
+
"datagrid",
|
|
22
|
+
"tanstack-intent"
|
|
22
23
|
],
|
|
23
24
|
"type": "module",
|
|
24
|
-
"module": "dist/
|
|
25
|
-
"types": "dist/
|
|
25
|
+
"module": "dist/fesm2022/tanstack-angular-table.mjs",
|
|
26
|
+
"types": "dist/types/tanstack-angular-table.d.ts",
|
|
26
27
|
"exports": {
|
|
27
28
|
".": {
|
|
28
|
-
"types": "./dist/
|
|
29
|
-
"
|
|
30
|
-
|
|
29
|
+
"types": "./dist/types/tanstack-angular-table.d.ts",
|
|
30
|
+
"default": "./dist/fesm2022/tanstack-angular-table.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./static-functions": {
|
|
33
|
+
"types": "./dist/types/tanstack-angular-table-static-functions.d.ts",
|
|
34
|
+
"default": "./dist/fesm2022/tanstack-angular-table-static-functions.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./flex-render": {
|
|
37
|
+
"types": "./dist/types/tanstack-angular-table.d.ts",
|
|
31
38
|
"default": "./dist/fesm2022/tanstack-angular-table.mjs"
|
|
32
39
|
},
|
|
33
40
|
"./package.json": {
|
|
@@ -35,26 +42,39 @@
|
|
|
35
42
|
}
|
|
36
43
|
},
|
|
37
44
|
"engines": {
|
|
38
|
-
"node": ">=
|
|
45
|
+
"node": ">=18"
|
|
39
46
|
},
|
|
40
47
|
"files": [
|
|
41
48
|
"dist",
|
|
42
|
-
"src"
|
|
49
|
+
"src",
|
|
50
|
+
"static-functions",
|
|
51
|
+
"skills"
|
|
43
52
|
],
|
|
44
53
|
"dependencies": {
|
|
45
|
-
"
|
|
46
|
-
"
|
|
54
|
+
"@tanstack/angular-store": "^0.11.0",
|
|
55
|
+
"tslib": "^2.8.1",
|
|
56
|
+
"@tanstack/table-core": "9.0.0-alpha.52"
|
|
47
57
|
},
|
|
48
58
|
"devDependencies": {
|
|
49
|
-
"@analogjs/vite-plugin-angular": "^
|
|
50
|
-
"@angular
|
|
51
|
-
"@angular/
|
|
52
|
-
"@angular/platform-browser
|
|
53
|
-
"ng-packagr": "^
|
|
59
|
+
"@analogjs/vite-plugin-angular": "^2.5.2",
|
|
60
|
+
"@analogjs/vitest-angular": "^2.5.2",
|
|
61
|
+
"@angular/core": "^21.2.15",
|
|
62
|
+
"@angular/platform-browser": "^21.2.15",
|
|
63
|
+
"ng-packagr": "^21.2.3",
|
|
64
|
+
"typescript": "6.0.3"
|
|
54
65
|
},
|
|
55
66
|
"peerDependencies": {
|
|
56
|
-
"@angular/core": ">=
|
|
67
|
+
"@angular/core": ">=19"
|
|
57
68
|
},
|
|
58
69
|
"sideEffects": false,
|
|
59
|
-
"scripts": {
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "ng-packagr -p ng-package.json -c tsconfig.build.json && rimraf ./dist/package.json",
|
|
72
|
+
"build:types": "tsc --emitDeclarationOnly",
|
|
73
|
+
"clean": "rimraf ./build && rimraf ./dist",
|
|
74
|
+
"test:build": "publint --strict",
|
|
75
|
+
"test:eslint": "eslint ./src",
|
|
76
|
+
"test:lib": "vitest",
|
|
77
|
+
"test:lib:dev": "vitest --watch",
|
|
78
|
+
"test:types": "tsc && vitest --typecheck"
|
|
79
|
+
}
|
|
60
80
|
}
|
|
@@ -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
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Content Shapes — Full Reference
|
|
2
|
+
|
|
3
|
+
`columnDef.cell` / `header` / `footer` can return any of these — `FlexRender`
|
|
4
|
+
dispatches on the shape:
|
|
5
|
+
|
|
6
|
+
| Returned value | Renderer behavior |
|
|
7
|
+
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| **Primitive** (`string`, `number`, `null`, `undefined`) | `createEmbeddedView` on the host `ng-template`; value is the implicit `$implicit` (so `let value` gets the primitive). |
|
|
9
|
+
| **`TemplateRef`** | `createEmbeddedView` of the template; the render context (`CellContext` / `HeaderContext`) is passed as `$implicit`. |
|
|
10
|
+
| **Component type** (`Type<T>`) | `createComponent` via `ViewContainerRef`; every property of the render context is forwarded via `ComponentRef.setInput(...)` to matching `input()` / `@Input()` slots on the component. |
|
|
11
|
+
| **`flexRenderComponent(Component, options)`** wrapper | `createComponent`, applying the wrapper's explicit `inputs`, `outputs`, `bindings`, `directives`, and optional `injector`. |
|
|
12
|
+
|
|
13
|
+
Render functions run inside `runInInjectionContext`, so you can call `inject()`,
|
|
14
|
+
read signals, and access DI tokens from within `cell: () => …`.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Returning a primitive
|
|
19
|
+
|
|
20
|
+
The most common cell — read the value and return it.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const columns: Array<ColumnDef<typeof _features, Person>> = [
|
|
24
|
+
{
|
|
25
|
+
accessorKey: 'firstName',
|
|
26
|
+
header: 'First name',
|
|
27
|
+
cell: (info) => info.getValue(),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
accessorKey: 'age',
|
|
31
|
+
header: () => 'Age',
|
|
32
|
+
cell: (info) => info.getValue(),
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use the template's `let value` for the primitive:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<td *flexRenderCell="cell; let value">{{ value }}</td>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If the primitive is HTML you trust, use `[innerHTML]`:
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<td *flexRenderCell="cell; let value">
|
|
47
|
+
<div [innerHTML]="value"></div>
|
|
48
|
+
</td>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Returning a `TemplateRef`
|
|
54
|
+
|
|
55
|
+
Capture a template with `viewChild(...)`, then return it from the column def.
|
|
56
|
+
The render context arrives as `$implicit`, so use `let context`:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { Component, TemplateRef, viewChild } from '@angular/core'
|
|
60
|
+
import type {
|
|
61
|
+
CellContext,
|
|
62
|
+
ColumnDef,
|
|
63
|
+
HeaderContext,
|
|
64
|
+
} from '@tanstack/angular-table'
|
|
65
|
+
|
|
66
|
+
@Component({
|
|
67
|
+
template: `
|
|
68
|
+
<ng-template #ageHeader let-context>
|
|
69
|
+
Age (sorted: {{ context.column.getIsSorted() }})
|
|
70
|
+
</ng-template>
|
|
71
|
+
|
|
72
|
+
<ng-template #ageCell let-context>
|
|
73
|
+
<strong>{{ context.getValue() }}</strong>
|
|
74
|
+
</ng-template>
|
|
75
|
+
`,
|
|
76
|
+
})
|
|
77
|
+
export class App {
|
|
78
|
+
readonly ageHeader =
|
|
79
|
+
viewChild.required<
|
|
80
|
+
TemplateRef<{ $implicit: HeaderContext<any, any, any> }>
|
|
81
|
+
>('ageHeader')
|
|
82
|
+
readonly ageCell =
|
|
83
|
+
viewChild.required<TemplateRef<{ $implicit: CellContext<any, any, any> }>>(
|
|
84
|
+
'ageCell',
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
readonly columns: Array<ColumnDef<any, any>> = [
|
|
88
|
+
{
|
|
89
|
+
accessorKey: 'age',
|
|
90
|
+
header: () => this.ageHeader(),
|
|
91
|
+
cell: () => this.ageCell(),
|
|
92
|
+
},
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`TemplateRef` rendering creates an injector that includes the DI context tokens,
|
|
98
|
+
so descendants inside the template can call `injectTableCellContext()`,
|
|
99
|
+
`injectTableHeaderContext()`, etc.
|
|
100
|
+
|
|
101
|
+
For reusable render blocks shared across screens, prefer a component over a
|
|
102
|
+
`TemplateRef`.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Returning an Angular component
|
|
107
|
+
|
|
108
|
+
You can return a component class directly. The renderer calls `setInput(name, value)`
|
|
109
|
+
for every property on the render context — define `input()` (or
|
|
110
|
+
`input.required()`) signals matching the names you want.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { Component, input } from '@angular/core'
|
|
114
|
+
import type { ColumnDef, Table } from '@tanstack/angular-table'
|
|
115
|
+
|
|
116
|
+
@Component({
|
|
117
|
+
selector: 'app-select-all',
|
|
118
|
+
template: `
|
|
119
|
+
<input
|
|
120
|
+
type="checkbox"
|
|
121
|
+
[checked]="table().getIsAllRowsSelected()"
|
|
122
|
+
[indeterminate]="table().getIsSomeRowsSelected()"
|
|
123
|
+
(change)="table().toggleAllRowsSelected()"
|
|
124
|
+
/>
|
|
125
|
+
`,
|
|
126
|
+
})
|
|
127
|
+
export class SelectAllComponent<T> {
|
|
128
|
+
readonly table = input.required<Table<any, T>>()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const columns: Array<ColumnDef<typeof _features, Person>> = [
|
|
132
|
+
{
|
|
133
|
+
id: 'select',
|
|
134
|
+
header: () => SelectAllComponent,
|
|
135
|
+
cell: () => RowSelectComponent,
|
|
136
|
+
},
|
|
137
|
+
]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> **Only inputs declared with `input()` / `input.required()` are set.** Other
|
|
141
|
+
> context properties are silently ignored at the input-binding level — but they
|
|
142
|
+
> are still reachable via `injectFlexRenderContext()`.
|