@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.
Files changed (59) hide show
  1. package/README.md +127 -0
  2. package/dist/README.md +127 -0
  3. package/dist/fesm2022/tanstack-angular-table-static-functions.mjs +6 -0
  4. package/dist/fesm2022/tanstack-angular-table-static-functions.mjs.map +1 -0
  5. package/dist/fesm2022/tanstack-angular-table.mjs +1357 -242
  6. package/dist/fesm2022/tanstack-angular-table.mjs.map +1 -1
  7. package/dist/types/tanstack-angular-table-static-functions.d.ts +1 -0
  8. package/dist/types/tanstack-angular-table.d.ts +793 -0
  9. package/package.json +39 -19
  10. package/skills/angular/angular-rendering-directives/SKILL.md +415 -0
  11. package/skills/angular/angular-rendering-directives/references/content-shapes.md +142 -0
  12. package/skills/angular/angular-rendering-directives/references/create-table-hook-registries.md +89 -0
  13. package/skills/angular/angular-rendering-directives/references/di-tokens.md +171 -0
  14. package/skills/angular/angular-rendering-directives/references/flex-render-component-options.md +64 -0
  15. package/skills/angular/client-to-server/SKILL.md +467 -0
  16. package/skills/angular/compose-with-tanstack-query/SKILL.md +482 -0
  17. package/skills/angular/compose-with-tanstack-store/SKILL.md +397 -0
  18. package/skills/angular/compose-with-tanstack-virtual/SKILL.md +400 -0
  19. package/skills/angular/getting-started/SKILL.md +496 -0
  20. package/skills/angular/getting-started/references/feature-row-model-mapping.md +48 -0
  21. package/skills/angular/migrate-v8-to-v9/SKILL.md +419 -0
  22. package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +261 -0
  23. package/skills/angular/production-readiness/SKILL.md +469 -0
  24. package/skills/angular/table-state/SKILL.md +429 -0
  25. package/skills/angular/table-state/references/external-atoms-and-app-hook.md +152 -0
  26. package/src/flex-render/context.ts +14 -0
  27. package/src/flex-render/flags.ts +34 -0
  28. package/src/flex-render/flexRenderComponent.ts +288 -0
  29. package/src/flex-render/flexRenderComponentFactory.ts +251 -0
  30. package/src/flex-render/renderer.ts +393 -0
  31. package/src/flex-render/view.ts +226 -0
  32. package/src/flexRender.ts +124 -0
  33. package/src/helpers/cell.ts +104 -0
  34. package/src/helpers/createTableHook.ts +499 -0
  35. package/src/helpers/flexRenderCell.ts +136 -0
  36. package/src/helpers/header.ts +99 -0
  37. package/src/helpers/table.ts +85 -0
  38. package/src/index.ts +23 -70
  39. package/src/injectTable.ts +206 -0
  40. package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
  41. package/src/reactivity.ts +147 -0
  42. package/static-functions/index.ts +1 -0
  43. package/static-functions/ng-package.json +6 -0
  44. package/dist/esm2022/flex-render.mjs +0 -150
  45. package/dist/esm2022/index.mjs +0 -48
  46. package/dist/esm2022/lazy-signal-initializer.mjs +0 -43
  47. package/dist/esm2022/proxy.mjs +0 -83
  48. package/dist/esm2022/tanstack-angular-table.mjs +0 -5
  49. package/dist/flex-render.d.ts +0 -31
  50. package/dist/index.d.ts +0 -5
  51. package/dist/lazy-signal-initializer.d.ts +0 -5
  52. package/dist/proxy.d.ts +0 -3
  53. package/src/__tests__/createAngularTable.test.ts +0 -95
  54. package/src/__tests__/flex-render.test.ts +0 -178
  55. package/src/__tests__/lazy-init.test.ts +0 -124
  56. package/src/__tests__/test-setup.ts +0 -12
  57. package/src/__tests__/test-utils.ts +0 -62
  58. package/src/flex-render.ts +0 -187
  59. 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.5",
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/esm2022/index.mjs",
25
- "types": "dist/index.d.ts",
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/index.d.ts",
29
- "esm": "./dist/esm2022/index.mjs",
30
- "esm2022": "./dist/esm2022/index.mjs",
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": ">=12"
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
- "tslib": "^2.6.3",
46
- "@tanstack/table-core": "9.0.0-alpha.4"
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": "^1.5.0",
50
- "@angular/core": "^18.0.3",
51
- "@angular/platform-browser": "^18.0.3",
52
- "@angular/platform-browser-dynamic": "^18.0.3",
53
- "ng-packagr": "^18.0.0"
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": ">=17"
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()`.