@tanstack/angular-table 9.0.0-alpha.9 → 9.0.0-beta.1
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 +1336 -239
- 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 +212 -0
- package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
- package/src/reactivity.ts +105 -0
- package/static-functions/index.ts +1 -0
- package/static-functions/ng-package.json +6 -0
- package/dist/esm2022/flex-render.mjs +0 -148
- 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 -30
- 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/flex-render.ts +0 -184
- package/src/proxy.ts +0 -97
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular/getting-started
|
|
3
|
+
description: >
|
|
4
|
+
End-to-end first-table journey for TanStack Table v9 in Angular: install
|
|
5
|
+
`@tanstack/angular-table`, declare `features` with `tableFeatures()`, register row-model
|
|
6
|
+
factories under `rowModels` with explicit `*Fns` parameters, build columns with the
|
|
7
|
+
`TFeatures, TData` generic order, call `injectTable(() => ({...}))` from an injection context,
|
|
8
|
+
and render with `FlexRender` / `*flexRenderHeader` / `*flexRenderCell` / `*flexRenderFooter`.
|
|
9
|
+
Covers the minimum-viable signal-backed table plus the upgrade path to sorting + filtering +
|
|
10
|
+
pagination.
|
|
11
|
+
type: lifecycle
|
|
12
|
+
library: tanstack-table
|
|
13
|
+
framework: angular
|
|
14
|
+
library_version: '9.0.0-alpha.48'
|
|
15
|
+
requires:
|
|
16
|
+
- angular/table-state
|
|
17
|
+
- angular/angular-rendering-directives
|
|
18
|
+
- setup
|
|
19
|
+
- column-definitions
|
|
20
|
+
sources:
|
|
21
|
+
- TanStack/table:docs/framework/angular/angular-table.md
|
|
22
|
+
- TanStack/table:docs/framework/angular/guide/table-state.md
|
|
23
|
+
- TanStack/table:docs/framework/angular/guide/rendering.md
|
|
24
|
+
- TanStack/table:packages/angular-table/src/injectTable.ts
|
|
25
|
+
- TanStack/table:examples/angular/basic-inject-table/
|
|
26
|
+
- TanStack/table:examples/angular/basic-app-table/
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Getting Started — Angular Table v9
|
|
30
|
+
|
|
31
|
+
> Goal: from zero to a working signal-backed, sorted + paginated, type-safe
|
|
32
|
+
> table in Angular ≥19.
|
|
33
|
+
>
|
|
34
|
+
> v9 is **explicit**: tell the table which features you want with `features`,
|
|
35
|
+
> tell it which row models you want with `rowModels`. That explicitness is what
|
|
36
|
+
> makes the v9 bundle tree-shakeable.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 1. Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm add @tanstack/angular-table
|
|
44
|
+
# or npm / yarn / bun
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Requires Angular ≥19 (signal APIs, `input()`, structural directive metadata).
|
|
48
|
+
Standalone components are assumed.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. The simplest possible table (core only)
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// app.ts
|
|
56
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
|
|
57
|
+
import {
|
|
58
|
+
FlexRender,
|
|
59
|
+
injectTable,
|
|
60
|
+
tableFeatures,
|
|
61
|
+
type ColumnDef,
|
|
62
|
+
} from '@tanstack/angular-table'
|
|
63
|
+
|
|
64
|
+
type Person = {
|
|
65
|
+
id: string
|
|
66
|
+
firstName: string
|
|
67
|
+
lastName: string
|
|
68
|
+
age: number
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 1. features OUTSIDE the component class (stable reference)
|
|
72
|
+
const features = tableFeatures({}) // empty = core row model only
|
|
73
|
+
|
|
74
|
+
// 2. columns OUTSIDE the component class (stable reference)
|
|
75
|
+
const columns: Array<ColumnDef<typeof features, Person>> = [
|
|
76
|
+
{
|
|
77
|
+
accessorKey: 'firstName',
|
|
78
|
+
header: 'First name',
|
|
79
|
+
cell: (info) => info.getValue(),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
accessorKey: 'lastName',
|
|
83
|
+
header: 'Last name',
|
|
84
|
+
cell: (info) => info.getValue(),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
accessorKey: 'age',
|
|
88
|
+
header: () => 'Age',
|
|
89
|
+
cell: (info) => info.getValue(),
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
@Component({
|
|
94
|
+
selector: 'app-root',
|
|
95
|
+
imports: [FlexRender], // tuple imports BOTH directives
|
|
96
|
+
templateUrl: './app.html',
|
|
97
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
98
|
+
})
|
|
99
|
+
export class App {
|
|
100
|
+
readonly data = signal<Array<Person>>([
|
|
101
|
+
{ id: '1', firstName: 'Ada', lastName: 'Lovelace', age: 36 },
|
|
102
|
+
{ id: '2', firstName: 'Alan', lastName: 'Turing', age: 41 },
|
|
103
|
+
])
|
|
104
|
+
|
|
105
|
+
// 3. injectTable in an injection context (a class field qualifies)
|
|
106
|
+
readonly table = injectTable(() => ({
|
|
107
|
+
features, // required in v9
|
|
108
|
+
rowModels: {}, // {} = core only; that's fine
|
|
109
|
+
columns, // stable ref
|
|
110
|
+
data: this.data(), // signal read → re-syncs the table on change
|
|
111
|
+
}))
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<!-- app.html -->
|
|
117
|
+
<table>
|
|
118
|
+
<thead>
|
|
119
|
+
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
|
|
120
|
+
<tr>
|
|
121
|
+
@for (header of headerGroup.headers; track header.id) {
|
|
122
|
+
<th>
|
|
123
|
+
@if (!header.isPlaceholder) {
|
|
124
|
+
<ng-container *flexRenderHeader="header; let value">
|
|
125
|
+
{{ value }}
|
|
126
|
+
</ng-container>
|
|
127
|
+
}
|
|
128
|
+
</th>
|
|
129
|
+
}
|
|
130
|
+
</tr>
|
|
131
|
+
}
|
|
132
|
+
</thead>
|
|
133
|
+
|
|
134
|
+
<tbody>
|
|
135
|
+
@for (row of table.getRowModel().rows; track row.id) {
|
|
136
|
+
<tr>
|
|
137
|
+
@for (cell of row.getVisibleCells(); track cell.id) {
|
|
138
|
+
<td>
|
|
139
|
+
<ng-container *flexRenderCell="cell; let value">
|
|
140
|
+
{{ value }}
|
|
141
|
+
</ng-container>
|
|
142
|
+
</td>
|
|
143
|
+
}
|
|
144
|
+
</tr>
|
|
145
|
+
}
|
|
146
|
+
</tbody>
|
|
147
|
+
</table>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
That's a complete v9 table. No sorting, no pagination — just `<table>` markup
|
|
151
|
+
driven by the row model.
|
|
152
|
+
|
|
153
|
+
### What the boilerplate is doing
|
|
154
|
+
|
|
155
|
+
- `tableFeatures({})` registers no opt-in features. The core row model
|
|
156
|
+
(`getRowModel()`) is always available. With `features: tableFeatures({})`,
|
|
157
|
+
`table.atoms.*` only contains the slices core ships with — no `pagination`,
|
|
158
|
+
no `sorting`, no `rowSelection` until you add the matching features.
|
|
159
|
+
- `rowModels: {}` does not register any feature-specific row models. Core
|
|
160
|
+
row model is included automatically.
|
|
161
|
+
- `injectTable(() => ({...}))` runs the initializer, builds the table, and
|
|
162
|
+
re-runs the initializer whenever any signal read inside changes. Stable
|
|
163
|
+
references outside the initializer keep `columns` / `features` / `rowModels`
|
|
164
|
+
from getting recreated on every data update.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 3. Add a feature — sorting
|
|
169
|
+
|
|
170
|
+
Each opt-in feature has two pieces in v9:
|
|
171
|
+
|
|
172
|
+
1. The **feature** itself (`rowSortingFeature`) in `features` — adds APIs
|
|
173
|
+
like `column.toggleSorting()` and the `sorting` state slice.
|
|
174
|
+
2. The **row-model factory** (`createSortedRowModel(sortFns)`) in `rowModels`
|
|
175
|
+
— produces the sorted output. Without it, `table.getRowModel().rows` is
|
|
176
|
+
unsorted regardless of sort state.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import {
|
|
180
|
+
injectTable,
|
|
181
|
+
tableFeatures,
|
|
182
|
+
rowSortingFeature,
|
|
183
|
+
createSortedRowModel,
|
|
184
|
+
sortFns,
|
|
185
|
+
type ColumnDef,
|
|
186
|
+
} from '@tanstack/angular-table'
|
|
187
|
+
|
|
188
|
+
const features = tableFeatures({
|
|
189
|
+
rowSortingFeature,
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
readonly table = injectTable(() => ({
|
|
193
|
+
features,
|
|
194
|
+
rowModels: {
|
|
195
|
+
sortedRowModel: createSortedRowModel(sortFns), // <-- enables sorting output
|
|
196
|
+
},
|
|
197
|
+
columns,
|
|
198
|
+
data: this.data(),
|
|
199
|
+
}))
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
In the template, drive sorting from the header:
|
|
203
|
+
|
|
204
|
+
```html
|
|
205
|
+
@if (!header.isPlaceholder) {
|
|
206
|
+
<th
|
|
207
|
+
(click)="header.column.toggleSorting()"
|
|
208
|
+
[style.cursor]="header.column.getCanSort() ? 'pointer' : ''"
|
|
209
|
+
>
|
|
210
|
+
<ng-container *flexRenderHeader="header; let value">{{ value }}</ng-container>
|
|
211
|
+
@switch (header.column.getIsSorted()) { @case ('asc') { ▲ } @case ('desc') { ▼
|
|
212
|
+
} }
|
|
213
|
+
</th>
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
> **Use `column.toggleSorting()`, not your own sort handler.** It correctly
|
|
218
|
+
> handles the asc → desc → unsorted cycle. Same applies for every other
|
|
219
|
+
> feature.
|
|
220
|
+
|
|
221
|
+
`sortFns` is the registry of built-in sort functions
|
|
222
|
+
(`alphanumeric`, `basic`, `datetime`, etc.). Pass only the ones you use to
|
|
223
|
+
tree-shake (`createSortedRowModel({ basic: sortFns.basic })`), or pass `sortFns`
|
|
224
|
+
in its entirety for all of them.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 4. Add filtering + pagination
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import {
|
|
232
|
+
injectTable,
|
|
233
|
+
tableFeatures,
|
|
234
|
+
rowSortingFeature,
|
|
235
|
+
columnFilteringFeature,
|
|
236
|
+
rowPaginationFeature,
|
|
237
|
+
createSortedRowModel,
|
|
238
|
+
createFilteredRowModel,
|
|
239
|
+
createPaginatedRowModel,
|
|
240
|
+
sortFns,
|
|
241
|
+
filterFns,
|
|
242
|
+
type ColumnDef,
|
|
243
|
+
} from '@tanstack/angular-table'
|
|
244
|
+
|
|
245
|
+
const features = tableFeatures({
|
|
246
|
+
rowSortingFeature,
|
|
247
|
+
columnFilteringFeature,
|
|
248
|
+
rowPaginationFeature,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
readonly table = injectTable(() => ({
|
|
252
|
+
features,
|
|
253
|
+
rowModels: {
|
|
254
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
255
|
+
filteredRowModel: createFilteredRowModel(filterFns),
|
|
256
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
257
|
+
},
|
|
258
|
+
columns,
|
|
259
|
+
data: this.data(),
|
|
260
|
+
initialState: {
|
|
261
|
+
pagination: { pageIndex: 0, pageSize: 10 },
|
|
262
|
+
},
|
|
263
|
+
}))
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Pagination controls — again, prefer the table APIs:
|
|
267
|
+
|
|
268
|
+
```html
|
|
269
|
+
<button (click)="table.previousPage()" [disabled]="!table.getCanPreviousPage()">
|
|
270
|
+
‹
|
|
271
|
+
</button>
|
|
272
|
+
<span>
|
|
273
|
+
Page {{ table.atoms.pagination.get().pageIndex + 1 }} of {{
|
|
274
|
+
table.getPageCount() }}
|
|
275
|
+
</span>
|
|
276
|
+
<button (click)="table.nextPage()" [disabled]="!table.getCanNextPage()">
|
|
277
|
+
›
|
|
278
|
+
</button>
|
|
279
|
+
|
|
280
|
+
<select
|
|
281
|
+
[value]="table.atoms.pagination.get().pageSize"
|
|
282
|
+
(change)="table.setPageSize(Number($any($event.target).value))"
|
|
283
|
+
>
|
|
284
|
+
@for (size of [10, 20, 50]; track size) {
|
|
285
|
+
<option [value]="size">{{ size }}</option>
|
|
286
|
+
}
|
|
287
|
+
</select>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Reading state in the template via `table.atoms.<slice>.get()` is signal-backed
|
|
291
|
+
— Angular tracks it and re-renders on change.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 5. Use the column helper for safer types
|
|
296
|
+
|
|
297
|
+
`createColumnHelper<TFeatures, TData>()` (generic order: features first!) gives
|
|
298
|
+
type-safe accessor / display / group definitions, plus a `columns(...)` method
|
|
299
|
+
for better inference across heterogeneous columns:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import { createColumnHelper } from '@tanstack/angular-table'
|
|
303
|
+
|
|
304
|
+
const columnHelper = createColumnHelper<typeof features, Person>()
|
|
305
|
+
|
|
306
|
+
const columns = columnHelper.columns([
|
|
307
|
+
columnHelper.accessor('firstName', {
|
|
308
|
+
header: 'First name',
|
|
309
|
+
cell: (info) => info.getValue(),
|
|
310
|
+
}),
|
|
311
|
+
columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
|
|
312
|
+
id: 'fullName',
|
|
313
|
+
header: 'Full name',
|
|
314
|
+
}),
|
|
315
|
+
columnHelper.display({
|
|
316
|
+
id: 'actions',
|
|
317
|
+
header: 'Actions',
|
|
318
|
+
cell: ({ row }) => `Edit #${row.original.id}`,
|
|
319
|
+
}),
|
|
320
|
+
])
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
> v9 changed the generic order: `createColumnHelper<typeof features, Person>()`,
|
|
324
|
+
> **not** `createColumnHelper<Person>()`. Same for `ColumnDef<typeof features, Person>`.
|
|
325
|
+
|
|
326
|
+
If multiple components share the same `features` / `rowModels`, factor them
|
|
327
|
+
into a `createTableHook(...)` call — see
|
|
328
|
+
`tanstack-table/angular/angular-rendering-directives` §10 and the
|
|
329
|
+
`composable-tables` example.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 6. Stable row identity — set `getRowId`
|
|
334
|
+
|
|
335
|
+
If your rows have a primary key, set `getRowId`. This makes row selection,
|
|
336
|
+
row pinning, and refetch-based updates correct.
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
readonly table = injectTable(() => ({
|
|
340
|
+
features,
|
|
341
|
+
rowModels: { /* … */ },
|
|
342
|
+
columns,
|
|
343
|
+
data: this.data(),
|
|
344
|
+
getRowId: (row) => row.id, // ← stable ID across re-fetches
|
|
345
|
+
}))
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Without `getRowId`, the row index becomes the ID — selection state
|
|
349
|
+
("rows 0–4 selected") survives sorting but breaks across server refetches that
|
|
350
|
+
return rows in a new order.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 7. State ownership — start with internal, hoist when you need to
|
|
355
|
+
|
|
356
|
+
The simplest table lets TanStack Table own all state internally. You set
|
|
357
|
+
starting values with `initialState`, and you use APIs like `table.nextPage()`
|
|
358
|
+
and `table.setSorting(...)` to drive updates.
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
readonly table = injectTable(() => ({
|
|
362
|
+
features,
|
|
363
|
+
rowModels: { /* … */ },
|
|
364
|
+
columns,
|
|
365
|
+
data: this.data(),
|
|
366
|
+
initialState: {
|
|
367
|
+
pagination: { pageIndex: 0, pageSize: 25 },
|
|
368
|
+
sorting: [{ id: 'age', desc: true }],
|
|
369
|
+
},
|
|
370
|
+
}))
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Hoist a slice into an Angular signal only when something outside the table
|
|
374
|
+
needs to read or react to it (URL sync, debounced server fetch, persistence,
|
|
375
|
+
cross-component coordination). The pattern is `state` + `on[State]Change` →
|
|
376
|
+
see `tanstack-table/angular/table-state` §6.
|
|
377
|
+
|
|
378
|
+
For full server-driven tables, see `tanstack-table/angular/client-to-server`.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Failure modes
|
|
383
|
+
|
|
384
|
+
### 1. (CRITICAL) Calling `injectTable` outside an injection context
|
|
385
|
+
|
|
386
|
+
`injectTable` calls `assertInInjectionContext`. It must be invoked from a
|
|
387
|
+
class-field initializer, constructor, or factory inside a DI scope. Calling it
|
|
388
|
+
from a service method or a `setTimeout` callback throws:
|
|
389
|
+
|
|
390
|
+
> `NG0203: inject() must be called from an injection context...`
|
|
391
|
+
|
|
392
|
+
If you need to construct a table from a service method, capture the injector
|
|
393
|
+
and use `runInInjectionContext(injector, () => injectTable(...))`.
|
|
394
|
+
|
|
395
|
+
### 2. (CRITICAL) Hallucinating v8 `createAngularTable` or `getCoreRowModel()`
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
// ❌ v8
|
|
399
|
+
import { createAngularTable, getCoreRowModel } from '@tanstack/angular-table'
|
|
400
|
+
|
|
401
|
+
// ✅ v9
|
|
402
|
+
import { injectTable, tableFeatures } from '@tanstack/angular-table'
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
There is no `getCoreRowModel()` / `getSortedRowModel()` / `getFilteredRowModel()`
|
|
406
|
+
in v9. Core row model is automatic; the rest are
|
|
407
|
+
`createSortedRowModel(sortFns)` / `createFilteredRowModel(filterFns)` / etc.
|
|
408
|
+
registered under `rowModels`.
|
|
409
|
+
|
|
410
|
+
### 3. (CRITICAL) Reimplementing what the table API already does
|
|
411
|
+
|
|
412
|
+
Telltale AI signs in a getting-started snippet:
|
|
413
|
+
|
|
414
|
+
- Custom `sortBy()` on the data signal instead of `table.setSorting()` /
|
|
415
|
+
`column.toggleSorting()`.
|
|
416
|
+
- Manual `pageIndex` math instead of `table.nextPage()` / `table.getCanNextPage()`.
|
|
417
|
+
- Computing `getCanNextPage()` as `pageIndex < Math.ceil(rows / pageSize) - 1`
|
|
418
|
+
instead of asking the table.
|
|
419
|
+
- Manual filtering of the data array before passing it to the table when you
|
|
420
|
+
could just register `columnFilteringFeature` + `createFilteredRowModel`.
|
|
421
|
+
|
|
422
|
+
The table already does all of this. Use it.
|
|
423
|
+
|
|
424
|
+
### 4. (HIGH) Feature without its row model (or vice versa)
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
// ❌ rowSortingFeature without createSortedRowModel → sort state changes, rows don't reorder
|
|
428
|
+
features: tableFeatures({ rowSortingFeature })
|
|
429
|
+
rowModels: {
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ✅
|
|
433
|
+
rowModels: {
|
|
434
|
+
sortedRowModel: createSortedRowModel(sortFns)
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Full mapping table → [`references/feature-row-model-mapping.md`](references/feature-row-model-mapping.md).
|
|
439
|
+
|
|
440
|
+
### 5. (HIGH) Declaring `columns` / `features` / `rowModels` inside the initializer
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
// ❌ Recreated on every signal change
|
|
444
|
+
readonly table = injectTable(() => ({
|
|
445
|
+
features: tableFeatures({ rowSortingFeature }),
|
|
446
|
+
rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
|
|
447
|
+
columns: [/* … */],
|
|
448
|
+
data: this.data(),
|
|
449
|
+
}))
|
|
450
|
+
|
|
451
|
+
// ✅ Stable references outside, signal reads inside
|
|
452
|
+
const features = tableFeatures({ rowSortingFeature })
|
|
453
|
+
const columns: Array<ColumnDef<typeof features, Person>> = [/* … */]
|
|
454
|
+
|
|
455
|
+
readonly table = injectTable(() => ({
|
|
456
|
+
features,
|
|
457
|
+
rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
|
|
458
|
+
columns,
|
|
459
|
+
data: this.data(),
|
|
460
|
+
}))
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### 6. (HIGH) Importing a non-existent `flexRender` function
|
|
464
|
+
|
|
465
|
+
In Angular, `FlexRender` is a directive tuple, not a function. There is no
|
|
466
|
+
`flexRender(fn, ctx)` call expression — that's the React/Vue API. Always:
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
import { FlexRender } from '@tanstack/angular-table'
|
|
470
|
+
@Component({ imports: [FlexRender] })
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
and use `*flexRenderCell` / `*flexRenderHeader` / `*flexRenderFooter` in the
|
|
474
|
+
template.
|
|
475
|
+
|
|
476
|
+
Lower-severity failure modes (MEDIUM: `createColumnHelper` generic-order flip,
|
|
477
|
+
importing only `FlexRenderDirective` without the shorthand) →
|
|
478
|
+
[`references/feature-row-model-mapping.md`](references/feature-row-model-mapping.md#lower-severity-failure-modes-medium).
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## References
|
|
483
|
+
|
|
484
|
+
- [Feature → row-model mapping table and MEDIUM failure modes](references/feature-row-model-mapping.md)
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## See also
|
|
489
|
+
|
|
490
|
+
- `tanstack-table/angular/table-state` — state model, ownership, controlled vs internal
|
|
491
|
+
- `tanstack-table/angular/angular-rendering-directives` — full rendering API surface
|
|
492
|
+
- `tanstack-table/angular/migrate-v8-to-v9` — for projects upgrading from v8
|
|
493
|
+
- `tanstack-table/angular/client-to-server` — flipping a working table to a server endpoint
|
|
494
|
+
- `tanstack-table/angular/production-readiness` — tree-shaking, stable refs, selectors
|
|
495
|
+
- Example: `examples/angular/basic-inject-table/`
|
|
496
|
+
- Example: `examples/angular/basic-app-table/` (uses `createTableHook`)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Feature → Row Model Mapping & Optional Patterns
|
|
2
|
+
|
|
3
|
+
Reference material extracted from the getting-started SKILL.md.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Feature → row model mapping
|
|
8
|
+
|
|
9
|
+
Every opt-in v9 feature has two pieces:
|
|
10
|
+
|
|
11
|
+
1. The **feature** itself in `features` — adds APIs (e.g.
|
|
12
|
+
`column.toggleSorting()`) and the matching state slice.
|
|
13
|
+
2. A **row-model factory** in `rowModels` — produces the derived row output.
|
|
14
|
+
Without it, sort/filter/paginate UI updates but rows don't reorder.
|
|
15
|
+
|
|
16
|
+
| Feature | Row model needed |
|
|
17
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
18
|
+
| `rowSortingFeature` | `sortedRowModel: createSortedRowModel(sortFns)` |
|
|
19
|
+
| `columnFilteringFeature` / `globalFilteringFeature` | `filteredRowModel: createFilteredRowModel(filterFns)` |
|
|
20
|
+
| `rowPaginationFeature` | `paginatedRowModel: createPaginatedRowModel()` |
|
|
21
|
+
| `rowExpandingFeature` | `expandedRowModel: createExpandedRowModel()` |
|
|
22
|
+
| `columnGroupingFeature` | `groupedRowModel: createGroupedRowModel(aggregationFns)` |
|
|
23
|
+
| `columnFacetingFeature` | `facetedRowModel: createFacetedRowModel()` (+ `facetedMinMaxValues` / `facetedUniqueValues`) |
|
|
24
|
+
| `rowSelectionFeature` | (no row model needed) |
|
|
25
|
+
| `columnVisibilityFeature` / `columnOrderingFeature` / `columnPinningFeature` / `columnSizingFeature` / `columnResizingFeature` / `rowPinningFeature` | (no row model needed) |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Lower-severity failure modes (MEDIUM)
|
|
30
|
+
|
|
31
|
+
### Wrong `createColumnHelper` generic order
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// ❌ v8 shape
|
|
35
|
+
const columnHelper = createColumnHelper<Person>()
|
|
36
|
+
|
|
37
|
+
// ✅ v9 — features first
|
|
38
|
+
const columnHelper = createColumnHelper<typeof features, Person>()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or use `createAppColumnHelper<Person>()` from a `createTableHook(...)` factory,
|
|
42
|
+
which pre-binds `TFeatures`.
|
|
43
|
+
|
|
44
|
+
### Importing only `FlexRenderDirective` and missing the shorthand
|
|
45
|
+
|
|
46
|
+
`FlexRender` is preferred — it imports both `FlexRenderDirective` (the long
|
|
47
|
+
`*flexRender`) and `FlexRenderCell` (the shorthand). If you only import one,
|
|
48
|
+
`*flexRenderCell` won't compile.
|