@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.
@@ -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()`.
@@ -0,0 +1,89 @@
1
+ # `createTableHook` — Component Registries Reference
2
+
3
+ `createTableHook(...)` returns Angular helpers with three optional component
4
+ registries:
5
+
6
+ | Registry | Lives on | Inject inside it with |
7
+ | ------------------ | --------------------------------------------------------------------------------- | ----------------------------------------------------------- |
8
+ | `tableComponents` | `table.<ComponentName>` (the table object) | `injectTableContext()` |
9
+ | `cellComponents` | the `Cell` prototype (`cell.<ComponentName>` after `table.appCell(cell)`) | `injectTableCellContext()` (or `injectFlexRenderContext()`) |
10
+ | `headerComponents` | the `Header` prototype (`header.<ComponentName>` after `table.appHeader(header)`) | `injectTableHeaderContext()` |
11
+
12
+ ```ts
13
+ import {
14
+ createTableHook,
15
+ tableFeatures,
16
+ rowSortingFeature,
17
+ } from '@tanstack/angular-table'
18
+ import { PaginationControls, RowCount } from './components/table-components'
19
+ import { TextCell, NumberCell, StatusCell } from './components/cell-components'
20
+ import { SortIndicator, ColumnFilter } from './components/header-components'
21
+
22
+ export const {
23
+ injectAppTable,
24
+ createAppColumnHelper,
25
+ injectTableContext,
26
+ injectTableCellContext,
27
+ injectTableHeaderContext,
28
+ } = createTableHook({
29
+ _features: tableFeatures({ rowSortingFeature }),
30
+ _rowModels: {
31
+ /* … */
32
+ },
33
+ tableComponents: { PaginationControls, RowCount },
34
+ cellComponents: { TextCell, NumberCell, StatusCell },
35
+ headerComponents: { SortIndicator, ColumnFilter },
36
+ })
37
+ ```
38
+
39
+ Use `NgComponentOutlet` to render table components:
40
+
41
+ ```html
42
+ <ng-container *ngComponentOutlet="table.PaginationControls" />
43
+ <ng-container *ngComponentOutlet="table.RowCount" />
44
+ ```
45
+
46
+ Reference cell components from column defs (type-safe through
47
+ `createAppColumnHelper<Person>()`):
48
+
49
+ ```ts
50
+ const columnHelper = createAppColumnHelper<Person>()
51
+
52
+ const columns = columnHelper.columns([
53
+ columnHelper.accessor('firstName', {
54
+ cell: ({ cell }) => cell.TextCell, // ✅ TS autocompletes registered components
55
+ }),
56
+ columnHelper.accessor('age', {
57
+ cell: ({ cell }) => cell.NumberCell,
58
+ }),
59
+ ])
60
+ ```
61
+
62
+ Headers via `table.appHeader(header)`:
63
+
64
+ ```html
65
+ @for (_h of headerGroup.headers; track _h.id) { @let header =
66
+ table.appHeader(_h);
67
+ <th>
68
+ <ng-container *flexRenderHeader="header; let value">
69
+ {{ value }}
70
+ </ng-container>
71
+ <ng-container
72
+ *flexRender="header.SortIndicator; props: header.getContext(); let v"
73
+ >
74
+ <div [innerHTML]="v"></div>
75
+ </ng-container>
76
+ </th>
77
+ }
78
+ ```
79
+
80
+ A header component is just an injection-context function or component:
81
+
82
+ ```ts
83
+ export function SortIndicator(): string | null {
84
+ const header = injectTableHeaderContext()
85
+ const sorted = header().column.getIsSorted()
86
+ if (!sorted) return null
87
+ return sorted === 'asc' ? '🔼' : '🔽'
88
+ }
89
+ ```
@@ -0,0 +1,171 @@
1
+ # DI Tokens & Context Injection — Full Reference
2
+
3
+ `FlexRender` automatically provides DI tokens for the render context to any
4
+ component rendered through `*flexRender` / `*flexRenderCell` / etc. The renderer
5
+ inspects the props object — if it has `table`, `header`, or `cell`, it
6
+ provides the matching token in the child injector.
7
+
8
+ | Helper | Returns | Available inside |
9
+ | ------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------ |
10
+ | `injectFlexRenderContext<T>()` | `T` (full props proxy) | a component rendered via `*flexRender` / `*flexRenderCell` / `*flexRenderHeader` / `*flexRenderFooter` |
11
+ | `injectTableContext<TData>()` | `Signal<Table<TFeatures, TData>>` | anything under `[tanStackTable]` or rendered via `*flexRender` whose context has `table` |
12
+ | `injectTableHeaderContext<TValue, TData>()` | `Signal<Header<...>>` | anything under `[tanStackTableHeader]` or rendered via `*flexRenderHeader` |
13
+ | `injectTableCellContext<TValue, TData>()` | `Signal<Cell<...>>` | anything under `[tanStackTableCell]` or rendered via `*flexRenderCell` |
14
+
15
+ The full props passed via `*flexRender`'s `props:` (or auto-derived by the
16
+ shorthand directives) is wrapped in a `Proxy` so property access always reads
17
+ the _latest_ value across re-renders.
18
+
19
+ ---
20
+
21
+ ## Pattern A — inject inside the rendered component itself
22
+
23
+ If your component **is** the cell, header, or footer (rendered through
24
+ `*flexRender*`), you don't need any extra directive — the token is auto-provided.
25
+
26
+ ```ts
27
+ @Component({
28
+ template: `
29
+ <span>{{ context.getValue() }}</span>
30
+ <button (click)="context.row.toggleSelected()">Toggle</button>
31
+ `,
32
+ })
33
+ export class InteractiveCell {
34
+ readonly context = injectFlexRenderContext<CellContext<any, any, any>>()
35
+ }
36
+ ```
37
+
38
+ Or, for just the cell signal:
39
+
40
+ ```ts
41
+ @Component({ template: `{{ cell().getValue() }}` })
42
+ export class TextCell {
43
+ readonly cell = injectTableCellContext<string>()
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Pattern B — context for _descendants outside the FlexRender tree_
50
+
51
+ When you have a component that lives next to the `*flexRenderCell` block, or
52
+ when you want any nested element to inject the table/header/cell, use the
53
+ context **host directives**:
54
+
55
+ ```ts
56
+ import {
57
+ FlexRender,
58
+ TanStackTable,
59
+ TanStackTableHeader,
60
+ TanStackTableCell,
61
+ } from '@tanstack/angular-table'
62
+
63
+ @Component({
64
+ imports: [FlexRender, TanStackTable, TanStackTableHeader, TanStackTableCell],
65
+ templateUrl: './app.html',
66
+ })
67
+ export class App {}
68
+ ```
69
+
70
+ ```html
71
+ <table [tanStackTable]="table">
72
+ @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
73
+ <tr>
74
+ @for (header of headerGroup.headers; track header.id) {
75
+ <th [tanStackTableHeader]="header">
76
+ <ng-container *flexRenderHeader="header; let value">
77
+ {{ value }}
78
+ </ng-container>
79
+ <!-- Any component here can call injectTableHeaderContext() -->
80
+ </th>
81
+ }
82
+ </tr>
83
+ } @for (row of table.getRowModel().rows; track row.id) {
84
+ <tr>
85
+ @for (cell of row.getVisibleCells(); track cell.id) {
86
+ <td [tanStackTableCell]="cell">
87
+ <ng-container *flexRenderCell="cell; let value">{{ value }}</ng-container>
88
+ <app-cell-actions />
89
+ <!-- ↑ cell-actions calls injectTableCellContext() — no input drilling -->
90
+ </td>
91
+ }
92
+ </tr>
93
+ }
94
+ </table>
95
+ ```
96
+
97
+ ```ts
98
+ @Component({
99
+ selector: 'app-cell-actions',
100
+ template: `<button (click)="cell().row.toggleSelected()">Toggle</button>`,
101
+ })
102
+ export class CellActionsComponent {
103
+ readonly cell = injectTableCellContext()
104
+ }
105
+ ```
106
+
107
+ Each directive uses Angular's `providers` array to register a per-host factory,
108
+ so contexts are correctly scoped — multiple `[tanStackTableCell]` directives on
109
+ sibling cells provide independent values.
110
+
111
+ ---
112
+
113
+ ## `*flexRender` directly (custom props)
114
+
115
+ Reach for the long form `*flexRender` when:
116
+
117
+ - You're rendering something that isn't `columnDef.cell` / `header` / `footer`
118
+ (e.g. a registered `headerComponents` member, see `createTableHook`).
119
+ - You want to override the props passed to the render function.
120
+
121
+ ```html
122
+ <ng-container
123
+ *flexRender="
124
+ cell.column.columnDef.cell;
125
+ props: cell.getContext();
126
+ let rendered
127
+ "
128
+ >
129
+ {{ rendered }}
130
+ </ng-container>
131
+ ```
132
+
133
+ Custom props (e.g. extending the default context):
134
+
135
+ ```html
136
+ <ng-container
137
+ *flexRender="
138
+ cell.column.columnDef.cell;
139
+ props: {
140
+ ...cell.getContext(),
141
+ extra: someValue(),
142
+ };
143
+ let rendered
144
+ "
145
+ >
146
+ {{ rendered }}
147
+ </ng-container>
148
+ ```
149
+
150
+ Inside the rendered component, `injectFlexRenderContext()` returns this full
151
+ props object.
152
+
153
+ You can also pass `flexRenderInjector:` to override the injector used for
154
+ `createComponent`.
155
+
156
+ ---
157
+
158
+ ## Render-function injection context
159
+
160
+ Because column-def `cell` / `header` / `footer` functions run inside
161
+ `runInInjectionContext`, this **works** at the top of those functions:
162
+
163
+ ```ts
164
+ cell: ({ getValue }) => {
165
+ const router = inject(Router) // ✅ legal
166
+ return router.url.endsWith('/admin') ? `[admin] ${getValue()}` : getValue()
167
+ }
168
+ ```
169
+
170
+ Be deliberate: this runs every time the cell renders. For per-app values that
171
+ don't change, prefer `inject(...)` at the component level and close over.
@@ -0,0 +1,64 @@
1
+ # `flexRenderComponent` — Full Options Reference
2
+
3
+ When you need custom inputs not derived from the render context, output
4
+ callbacks, a custom injector, or Angular v20+ `bindings` / `directives`,
5
+ **wrap the component**:
6
+
7
+ ```ts
8
+ import { flexRenderComponent, type ColumnDef } from '@tanstack/angular-table'
9
+ import { EditableCell } from './editable-cell'
10
+
11
+ const columns: Array<ColumnDef<typeof _features, Person>> = [
12
+ {
13
+ accessorKey: 'firstName',
14
+ cell: ({ getValue, row, column, table }) =>
15
+ flexRenderComponent(EditableCell, {
16
+ inputs: {
17
+ value: getValue(),
18
+ },
19
+ outputs: {
20
+ change: (value) => {
21
+ table.options.meta?.updateData(row.index, column.id, value)
22
+ },
23
+ },
24
+ }),
25
+ },
26
+ ]
27
+ ```
28
+
29
+ ## How inputs/outputs are wired
30
+
31
+ - **`inputs`** → applied via `ComponentRef.setInput(key, value)` (works with
32
+ both `input()` signals and legacy `@Input()`). Diffed per change-detection
33
+ cycle with `KeyValueDiffers` — unchanged values are _not_ re-set, so object
34
+ inputs are reference-checked. Keep input objects referentially stable when
35
+ you can.
36
+ - **`outputs`** → resolved at component-instance level. The wrapper reads the
37
+ property by name, checks it is an `OutputEmitterRef`, and subscribes. The
38
+ subscription is cleaned up when the component is destroyed.
39
+ - **`injector`** → use when the rendered component needs to inject from a
40
+ specific scope (e.g. a feature module / sub-injector).
41
+ - **`bindings`** (Angular v20+) → forwarded directly to
42
+ `ViewContainerRef.createComponent` at creation time. Use this with
43
+ `inputBinding`, `outputBinding`, `twoWayBinding` for native programmatic
44
+ rendering semantics.
45
+ - **`directives`** (Angular v20+) → host directives forwarded the same way.
46
+
47
+ ```ts
48
+ import { inputBinding, outputBinding, twoWayBinding, signal } from '@angular/core'
49
+
50
+ readonly name = signal('Ada')
51
+
52
+ cell: () =>
53
+ flexRenderComponent(EditableNameCell, {
54
+ bindings: [
55
+ inputBinding('value', this.name),
56
+ twoWayBinding('value', this.name),
57
+ outputBinding('valueChange', (v) => console.log('changed', v)),
58
+ ],
59
+ })
60
+ ```
61
+
62
+ > **Do not mix `bindings` with `inputs`/`outputs`** on the same component.
63
+ > `bindings` apply at creation time and participate in the initial CD cycle;
64
+ > `inputs`/`outputs` apply after. Mixing them risks double-initialization.