@tanstack/angular-table 9.0.0-alpha.5 → 9.0.0-alpha.51
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
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular/table-state
|
|
3
|
+
description: >
|
|
4
|
+
TanStack Table v9 state ownership in Angular: signal-backed atoms via `angularReactivity`,
|
|
5
|
+
the `injectTable(() => ({...}))` lazy initializer pattern, reading `table.atoms.<slice>.get()`
|
|
6
|
+
inside templates / `computed(...)` / `effect(...)`, `shallow` for object slices, controlled state
|
|
7
|
+
with Angular signals + `state` + `on[State]Change`, and when to reach for external TanStack Store
|
|
8
|
+
atoms instead. Required reading before any other Angular Table v9 skill.
|
|
9
|
+
type: framework
|
|
10
|
+
library: tanstack-table
|
|
11
|
+
framework: angular
|
|
12
|
+
library_version: '9.0.0-alpha.48'
|
|
13
|
+
requires:
|
|
14
|
+
- state-management
|
|
15
|
+
- setup
|
|
16
|
+
sources:
|
|
17
|
+
- TanStack/table:docs/framework/angular/angular-table.md
|
|
18
|
+
- TanStack/table:docs/framework/angular/guide/table-state.md
|
|
19
|
+
- TanStack/table:docs/framework/angular/guide/migrating.md
|
|
20
|
+
- TanStack/table:packages/angular-table/src/injectTable.ts
|
|
21
|
+
- TanStack/table:packages/angular-table/src/reactivity.ts
|
|
22
|
+
- TanStack/table:packages/angular-table/src/lazySignalInitializer.ts
|
|
23
|
+
- TanStack/table:examples/angular/basic-inject-table/
|
|
24
|
+
- TanStack/table:examples/angular/row-selection-signal/
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Angular Table State (v9)
|
|
28
|
+
|
|
29
|
+
> **TanStack Table is a state-management coordinator.** v9 rebuilt that coordinator
|
|
30
|
+
> on top of TanStack Store (`alien-signals`). In Angular, the adapter bridges those
|
|
31
|
+
> atoms to native Angular signals, so reading `table.atoms.<slice>.get()` from a
|
|
32
|
+
> template, `computed(...)`, or `effect(...)` participates in Angular reactivity.
|
|
33
|
+
> Prefer that direct atom read when you need a specific state slice.
|
|
34
|
+
>
|
|
35
|
+
> This is the prerequisite for every other Angular Table skill. Don't skip it.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. Prerequisites — `_features` and `_rowModels` decide what state exists
|
|
40
|
+
|
|
41
|
+
In v9, **a state slice only exists if its feature is registered in `_features`**.
|
|
42
|
+
This is the #1 v9-specific gotcha and the root cause of many "missing API"
|
|
43
|
+
TypeScript errors.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import {
|
|
47
|
+
injectTable,
|
|
48
|
+
tableFeatures,
|
|
49
|
+
rowPaginationFeature,
|
|
50
|
+
rowSortingFeature,
|
|
51
|
+
createPaginatedRowModel,
|
|
52
|
+
createSortedRowModel,
|
|
53
|
+
sortFns,
|
|
54
|
+
} from '@tanstack/angular-table'
|
|
55
|
+
|
|
56
|
+
// Declare features OUTSIDE the initializer (see §2 below)
|
|
57
|
+
const _features = tableFeatures({
|
|
58
|
+
rowPaginationFeature,
|
|
59
|
+
rowSortingFeature,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
readonly table = injectTable(() => ({
|
|
63
|
+
_features,
|
|
64
|
+
_rowModels: {
|
|
65
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
66
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
67
|
+
},
|
|
68
|
+
columns,
|
|
69
|
+
data: this.data(),
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
this.table.atoms.pagination.get() // ✅
|
|
73
|
+
this.table.atoms.sorting.get() // ✅
|
|
74
|
+
// this.table.atoms.rowSelection // ❌ TS error — rowSelectionFeature not registered
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If you see `Property 'atoms.rowSelection' does not exist` or
|
|
78
|
+
`table.toggleRowSelected is not a function`, **add the feature to `_features`** —
|
|
79
|
+
don't reach for `@ts-ignore`, don't reimplement the API, don't switch to
|
|
80
|
+
`stockFeatures` until you understand which features you actually need.
|
|
81
|
+
|
|
82
|
+
`tableFeatures({})` (empty) is valid — you get the core row model only.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 2. The `injectTable` lazy-initializer model
|
|
87
|
+
|
|
88
|
+
`injectTable` is the v9 entrypoint (replacing v8's `createAngularTable`). It must
|
|
89
|
+
run inside an Angular injection context (a component constructor / class field).
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
readonly table = injectTable(() => ({
|
|
93
|
+
_features,
|
|
94
|
+
_rowModels: {},
|
|
95
|
+
columns,
|
|
96
|
+
data: this.data(),
|
|
97
|
+
}))
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### The initializer is a `computed`-like function
|
|
101
|
+
|
|
102
|
+
The initializer runs **whenever any Angular signal read inside it changes**. The
|
|
103
|
+
adapter then calls `table.setOptions({ ...previous, ...newOptions })` to sync.
|
|
104
|
+
That means:
|
|
105
|
+
|
|
106
|
+
- **Reactive values that should re-sync the table** (`this.data()`, controlled
|
|
107
|
+
state signals) go _inside_ the initializer.
|
|
108
|
+
- **Stable references** (`columns`, `_features`, `_rowModels`, feature-fn maps)
|
|
109
|
+
go _outside_ — or you'll recreate the column model on every data update.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// ❌ WRONG — columns + _features recreated on every data change
|
|
113
|
+
readonly table = injectTable(() => ({
|
|
114
|
+
_features: tableFeatures({ rowSortingFeature }), // new reference each run
|
|
115
|
+
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, // ditto
|
|
116
|
+
columns: [/* … */], // ditto
|
|
117
|
+
data: this.data(),
|
|
118
|
+
}))
|
|
119
|
+
|
|
120
|
+
// ✅ Stable references outside, signal reads inside
|
|
121
|
+
const _features = tableFeatures({ rowSortingFeature })
|
|
122
|
+
const columns: Array<ColumnDef<typeof _features, Person>> = [/* … */]
|
|
123
|
+
|
|
124
|
+
readonly table = injectTable(() => ({
|
|
125
|
+
_features,
|
|
126
|
+
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
|
|
127
|
+
columns,
|
|
128
|
+
data: this.data(), // ← only the signal read should be inside
|
|
129
|
+
}))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### The returned table is signal-reactive too
|
|
133
|
+
|
|
134
|
+
The `table` returned by `injectTable` exposes APIs that read signal-backed atoms
|
|
135
|
+
internally, so calling `table.getRowModel()`, `table.getSelectedRowModel()`,
|
|
136
|
+
`table.atoms.pagination.get()`, etc. inside templates / `computed` / `effect`
|
|
137
|
+
_just works_ — no manual subscriptions.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 3. The three state surfaces
|
|
142
|
+
|
|
143
|
+
A table instance has three ways to look at its state:
|
|
144
|
+
|
|
145
|
+
| Surface | Shape | Use when |
|
|
146
|
+
| ------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------- |
|
|
147
|
+
| `table.baseAtoms.<slice>` | writable TanStack Store atom (always exists for registered slices) | low-level direct write; rare |
|
|
148
|
+
| `table.atoms.<slice>` | **readonly** derived atom per registered feature; backed by Angular `computed` | reading current value or driving reactivity |
|
|
149
|
+
| `table.state` | flat proxy object over every registered slice | full-state JSON/debug output |
|
|
150
|
+
|
|
151
|
+
All three are signal-backed in Angular. Reading any of them inside a template,
|
|
152
|
+
`computed(...)`, or `effect(...)` registers an Angular dependency. For normal
|
|
153
|
+
render code, prefer `table.atoms.<slice>.get()` so the read is explicit and
|
|
154
|
+
limited to the slice the component needs. Use `table.state` when you actually
|
|
155
|
+
need the flat full-state shape, such as `JSON.stringify(table.state, null, 2)`.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// Read current value (anywhere)
|
|
159
|
+
const pagination = this.table.atoms.pagination.get()
|
|
160
|
+
|
|
161
|
+
// Same value through the flat proxy; use mainly for full-state debug output
|
|
162
|
+
const pagination2 = this.table.state.pagination
|
|
163
|
+
|
|
164
|
+
// Reactive derivation with custom equality
|
|
165
|
+
import { computed } from '@angular/core'
|
|
166
|
+
import { shallow } from '@tanstack/angular-table'
|
|
167
|
+
|
|
168
|
+
readonly pageIndex = computed(
|
|
169
|
+
() => this.table.atoms.pagination.get().pageIndex,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
readonly pagination = computed(
|
|
173
|
+
() => this.table.atoms.pagination.get(),
|
|
174
|
+
{ equal: shallow }, // structural equality — skip downstream work on no-op updates
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### When do I need `computed(...)`?
|
|
179
|
+
|
|
180
|
+
You **don't need `computed`** just to make an atom reactive. The atom is already
|
|
181
|
+
signal-backed. Use `computed(...)` only when you want:
|
|
182
|
+
|
|
183
|
+
1. **Derivation** — `computed(() => this.table.atoms.pagination.get().pageIndex)`
|
|
184
|
+
2. **Custom equality** — `{ equal: shallow }` on object/array slices, so
|
|
185
|
+
downstream `effect`s skip no-op updates when the slice is recreated with the
|
|
186
|
+
same values.
|
|
187
|
+
3. **Caching of an expensive transformation** that reads from multiple atoms.
|
|
188
|
+
|
|
189
|
+
For plain reads in a template, `{{ table.atoms.pagination.get().pageIndex }}`
|
|
190
|
+
is fine.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 4. Setting state — use feature APIs, not direct writes
|
|
195
|
+
|
|
196
|
+
TanStack Table exposes a method for nearly every state transition. **Use those
|
|
197
|
+
methods.** Don't reimplement what's already in the public API — that's the #1
|
|
198
|
+
tell of AI-generated table code.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
// ✅ Use the API
|
|
202
|
+
this.table.setPageIndex(0)
|
|
203
|
+
this.table.nextPage()
|
|
204
|
+
this.table.setPageSize(25)
|
|
205
|
+
this.table.setSorting([{ id: 'age', desc: true }])
|
|
206
|
+
this.table.setColumnFilters([{ id: 'status', value: 'active' }])
|
|
207
|
+
this.table.toggleAllRowsSelected(true)
|
|
208
|
+
this.table.resetSorting()
|
|
209
|
+
this.table.resetPagination()
|
|
210
|
+
this.table.resetPagination(true) // reset to feature default, not initialState
|
|
211
|
+
row.toggleSelected()
|
|
212
|
+
column.toggleVisibility()
|
|
213
|
+
column.toggleSorting()
|
|
214
|
+
|
|
215
|
+
// ❌ Don't write to atoms directly unless you really have to
|
|
216
|
+
this.table.baseAtoms.pagination.set((old) => ({ ...old, pageIndex: 0 }))
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Direct `baseAtoms` writes bypass `on[State]Change` handlers and won't update
|
|
220
|
+
externally owned state — if you've controlled the slice with an Angular signal,
|
|
221
|
+
you must update the signal, not the base atom.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 5. Setting starting values — `initialState`
|
|
226
|
+
|
|
227
|
+
`initialState` is the single right place to seed registered slices. It is also
|
|
228
|
+
the value that reset APIs reset to.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
readonly table = injectTable(() => ({
|
|
232
|
+
_features,
|
|
233
|
+
_rowModels: { /* … */ },
|
|
234
|
+
columns,
|
|
235
|
+
data: this.data(),
|
|
236
|
+
initialState: {
|
|
237
|
+
sorting: [{ id: 'age', desc: true }],
|
|
238
|
+
pagination: { pageIndex: 0, pageSize: 25 },
|
|
239
|
+
},
|
|
240
|
+
}))
|
|
241
|
+
|
|
242
|
+
// Later
|
|
243
|
+
this.table.resetSorting() // → initialState.sorting
|
|
244
|
+
this.table.resetSorting(true) // → feature default ([])
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`initialState` only applies to slices whose feature is registered. Mutating
|
|
248
|
+
`initialState` after construction does **not** re-seed state — use it for
|
|
249
|
+
starting values only.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 6. Controlled state — the recommended Angular pattern
|
|
254
|
+
|
|
255
|
+
Most Angular Table apps that need cross-component access to a state slice use
|
|
256
|
+
**Angular signals + `state` + `on[State]Change`**. This keeps ownership in
|
|
257
|
+
Angular's signal model while `injectTable` keeps the table in sync.
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { signal } from '@angular/core'
|
|
261
|
+
import {
|
|
262
|
+
injectTable,
|
|
263
|
+
rowPaginationFeature,
|
|
264
|
+
rowSortingFeature,
|
|
265
|
+
tableFeatures,
|
|
266
|
+
type PaginationState,
|
|
267
|
+
type SortingState,
|
|
268
|
+
} from '@tanstack/angular-table'
|
|
269
|
+
|
|
270
|
+
const _features = tableFeatures({ rowPaginationFeature, rowSortingFeature })
|
|
271
|
+
|
|
272
|
+
export class Component {
|
|
273
|
+
readonly data = signal<Array<Person>>([])
|
|
274
|
+
readonly sorting = signal<SortingState>([])
|
|
275
|
+
readonly pagination = signal<PaginationState>({ pageIndex: 0, pageSize: 10 })
|
|
276
|
+
|
|
277
|
+
readonly table = injectTable(() => ({
|
|
278
|
+
_features,
|
|
279
|
+
_rowModels: {
|
|
280
|
+
/* … */
|
|
281
|
+
},
|
|
282
|
+
columns,
|
|
283
|
+
data: this.data(),
|
|
284
|
+
state: {
|
|
285
|
+
sorting: this.sorting(),
|
|
286
|
+
pagination: this.pagination(),
|
|
287
|
+
},
|
|
288
|
+
onSortingChange: (updater) => {
|
|
289
|
+
updater instanceof Function
|
|
290
|
+
? this.sorting.update(updater)
|
|
291
|
+
: this.sorting.set(updater)
|
|
292
|
+
},
|
|
293
|
+
onPaginationChange: (updater) => {
|
|
294
|
+
updater instanceof Function
|
|
295
|
+
? this.pagination.update(updater)
|
|
296
|
+
: this.pagination.set(updater)
|
|
297
|
+
},
|
|
298
|
+
}))
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### `on[State]Change` rules
|
|
303
|
+
|
|
304
|
+
- **Always pass an updater-or-value handler.** TanStack Table calls
|
|
305
|
+
`on[State]Change(updaterOrValue)` where `updaterOrValue` is either a new value
|
|
306
|
+
or `(old) => new` — check with `instanceof Function` / `typeof === 'function'`.
|
|
307
|
+
- **Pair `on[State]Change` with `state.<slice>`.** Providing
|
|
308
|
+
`onPaginationChange` without `state.pagination` will result in your callback
|
|
309
|
+
firing but the table reading its own internal atom — confusing.
|
|
310
|
+
- **The v8 `onStateChange` (single global handler) is gone in v9.** Slices are
|
|
311
|
+
controlled individually.
|
|
312
|
+
|
|
313
|
+
### Don't double-own a slice
|
|
314
|
+
|
|
315
|
+
For any given slice, exactly one of these should be the source of truth:
|
|
316
|
+
|
|
317
|
+
- `initialState.<slice>` (uncontrolled, internal)
|
|
318
|
+
- `state.<slice>` + `on[State]Change` (controlled by Angular signal)
|
|
319
|
+
- `atoms.<slice>` (controlled by external TanStack Store atom — see §7)
|
|
320
|
+
|
|
321
|
+
If you supply both `state.x` and `atoms.x`, the external atom wins silently. If
|
|
322
|
+
you supply both `initialState.x` and `state.x`, `state.x` wins. Pick one.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 7. Beyond signals: external atoms, state types, app-wide hooks
|
|
327
|
+
|
|
328
|
+
For most Angular apps, **signals + `state` + `on[State]Change` (§6) is the
|
|
329
|
+
right ownership model.** When you need more, see
|
|
330
|
+
[`references/external-atoms-and-app-hook.md`](references/external-atoms-and-app-hook.md):
|
|
331
|
+
|
|
332
|
+
- **External TanStack Store atoms** — `atoms: { pagination: paginationAtom }`
|
|
333
|
+
for slices owned by `@tanstack/store` / `@tanstack/angular-store`, when
|
|
334
|
+
multiple non-table parts of the app share the slice.
|
|
335
|
+
- **State type imports** — `PaginationState`, `SortingState`,
|
|
336
|
+
`RowSelectionState`, `TableState<typeof _features>`, etc.
|
|
337
|
+
- **`createTableHook(...)`** — app-wide `injectAppTable` /
|
|
338
|
+
`createAppColumnHelper` that pre-bind `_features` and `_rowModels`. Also
|
|
339
|
+
exposes `tableComponents` / `cellComponents` / `headerComponents` registries
|
|
340
|
+
(covered in `angular-rendering-directives`).
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Failure modes
|
|
345
|
+
|
|
346
|
+
### 1. (CRITICAL) Hallucinating v8 `createAngularTable` / pre-v9 APIs
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
// ❌ v8 — does not exist in v9
|
|
350
|
+
import { createAngularTable, getCoreRowModel } from '@tanstack/angular-table'
|
|
351
|
+
const table = createAngularTable(() => ({
|
|
352
|
+
columns,
|
|
353
|
+
data: data(),
|
|
354
|
+
getCoreRowModel: getCoreRowModel(),
|
|
355
|
+
}))
|
|
356
|
+
|
|
357
|
+
// ✅ v9
|
|
358
|
+
import { injectTable, tableFeatures } from '@tanstack/angular-table'
|
|
359
|
+
const _features = tableFeatures({})
|
|
360
|
+
const table = injectTable(() => ({
|
|
361
|
+
_features,
|
|
362
|
+
_rowModels: {},
|
|
363
|
+
columns,
|
|
364
|
+
data: data(),
|
|
365
|
+
}))
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Also retired: `getFilteredRowModel`, `getSortedRowModel`, `getPaginationRowModel`
|
|
369
|
+
as top-level options → migrated to `_rowModels: { filteredRowModel: ..., sortedRowModel: ..., paginatedRowModel: ... }`
|
|
370
|
+
with explicit `*Fns` parameters.
|
|
371
|
+
|
|
372
|
+
### 2. (CRITICAL) Missing API because feature not in `_features`
|
|
373
|
+
|
|
374
|
+
`table.atoms.rowSelection`, `table.toggleAllRowsSelected`,
|
|
375
|
+
`row.getCanSelect`, `column.getCanSort` etc. are **only** present when the
|
|
376
|
+
matching feature is in `_features`. The fix is to add the feature, not to
|
|
377
|
+
patch around it.
|
|
378
|
+
|
|
379
|
+
### 3. (CRITICAL) Reimplementing built-in state transitions
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
// ❌ DON'T
|
|
383
|
+
this.pagination.update((p) => ({ ...p, pageIndex: p.pageIndex + 1 }))
|
|
384
|
+
|
|
385
|
+
// ✅
|
|
386
|
+
this.table.nextPage()
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Same for `setPageIndex`, `setPageSize`, `setSorting`, `toggleSorting`,
|
|
390
|
+
`setColumnFilters`, `setGlobalFilter`, `toggleSelected`, `toggleAllRowsSelected`,
|
|
391
|
+
`setColumnVisibility`, `setColumnOrder`, `setExpanded`, `toggleExpanded`,
|
|
392
|
+
`resetSorting`, `resetPagination`, `resetRowSelection`, etc.
|
|
393
|
+
|
|
394
|
+
### 4. (HIGH) Expensive values declared **inside** the `injectTable` initializer
|
|
395
|
+
|
|
396
|
+
Because the initializer re-runs when any reactive read inside it changes,
|
|
397
|
+
declaring `columns`, `_features`, `_rowModels`, or feature-fn maps inside the
|
|
398
|
+
function causes them to be recreated and re-applied on every data update.
|
|
399
|
+
Move them outside the class or to stable class fields.
|
|
400
|
+
|
|
401
|
+
### 5. (HIGH) Forgetting that the initializer re-runs
|
|
402
|
+
|
|
403
|
+
If you `console.log` inside the `injectTable` initializer, you'll see it fire
|
|
404
|
+
multiple times during the component lifetime — that's correct. The adapter
|
|
405
|
+
handles the diff and calls `table.setOptions`. Don't kick off side-effects from
|
|
406
|
+
inside the initializer; put them in an `effect(...)` reading the relevant
|
|
407
|
+
atoms.
|
|
408
|
+
|
|
409
|
+
Lower-severity failure modes (MEDIUM/LOW: `state.x` vs `atoms.x` conflict,
|
|
410
|
+
updater-fn handling in `on[State]Change`, in-place mutation of state values,
|
|
411
|
+
premature `computed` wrapping) →
|
|
412
|
+
[`references/external-atoms-and-app-hook.md`](references/external-atoms-and-app-hook.md#lower-severity-failure-modes-mediumlow).
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## References
|
|
417
|
+
|
|
418
|
+
- [External TanStack Store atoms, state types, `createTableHook` setup, and MEDIUM failure modes](references/external-atoms-and-app-hook.md)
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## See also
|
|
423
|
+
|
|
424
|
+
- `tanstack-table/angular/getting-started` — end-to-end first table
|
|
425
|
+
- `tanstack-table/angular/angular-rendering-directives` — `*flexRender*`, DI tokens, `flexRenderComponent`
|
|
426
|
+
- `tanstack-table/angular/migrate-v8-to-v9` — v8 → v9 mechanical mapping
|
|
427
|
+
- `tanstack-table/angular/compose-with-tanstack-store` — external atoms in depth
|
|
428
|
+
- `tanstack-table/angular/client-to-server` — controlled state for server-driven tables
|
|
429
|
+
- `tanstack-table/core/state-management` — framework-agnostic atom model
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# External Atoms & `createTableHook` — Full Reference
|
|
2
|
+
|
|
3
|
+
Extended ownership options beyond signals + `state` + `on[State]Change`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## External TanStack Store atoms — alternative ownership
|
|
8
|
+
|
|
9
|
+
The core `atoms` table option is re-exported from Angular Table. Use it when:
|
|
10
|
+
|
|
11
|
+
- You already have a `Store` / `Atom` from `@tanstack/store` or
|
|
12
|
+
`@tanstack/angular-store` that should drive a table slice.
|
|
13
|
+
- Multiple non-table parts of the app need to read/write the slice through the
|
|
14
|
+
same atom (e.g. URL sync, persistence, multi-table coordination).
|
|
15
|
+
|
|
16
|
+
**For most Angular apps, prefer signals + `state` first.** Atoms are the
|
|
17
|
+
cross-app sharing alternative, not the default. See
|
|
18
|
+
`compose-with-tanstack-store` for the full pattern.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { Store } from '@tanstack/store'
|
|
22
|
+
|
|
23
|
+
const paginationAtom = new Store<PaginationState>({ pageIndex: 0, pageSize: 10 })
|
|
24
|
+
|
|
25
|
+
readonly table = injectTable(() => ({
|
|
26
|
+
_features,
|
|
27
|
+
_rowModels: { /* … */ },
|
|
28
|
+
columns,
|
|
29
|
+
data: this.data(),
|
|
30
|
+
atoms: {
|
|
31
|
+
pagination: paginationAtom, // external atom owns the slice
|
|
32
|
+
},
|
|
33
|
+
}))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
When `atoms.pagination` is registered, `table.atoms.pagination.get()` reads from
|
|
37
|
+
the external atom (not the internal base atom). `table.setPagination(...)`
|
|
38
|
+
writes through the external atom's setter.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## State types
|
|
43
|
+
|
|
44
|
+
Import slice types directly from `@tanstack/angular-table`:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import type {
|
|
48
|
+
PaginationState,
|
|
49
|
+
SortingState,
|
|
50
|
+
RowSelectionState,
|
|
51
|
+
ColumnFiltersState,
|
|
52
|
+
GlobalFilterTableState,
|
|
53
|
+
ColumnVisibilityState,
|
|
54
|
+
ColumnOrderState,
|
|
55
|
+
ColumnPinningState,
|
|
56
|
+
ColumnSizingState,
|
|
57
|
+
ColumnResizingState,
|
|
58
|
+
ExpandedState,
|
|
59
|
+
GroupingState,
|
|
60
|
+
TableState,
|
|
61
|
+
} from '@tanstack/angular-table'
|
|
62
|
+
|
|
63
|
+
// TableState<typeof _features> is inferred from registered features
|
|
64
|
+
type MyTableState = TableState<typeof _features>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## `createTableHook` — app-wide table infrastructure
|
|
70
|
+
|
|
71
|
+
When multiple tables in an app share the same `_features`, `_rowModels`, and
|
|
72
|
+
component conventions, factor them into a `createTableHook(...)` call once and
|
|
73
|
+
import the resulting `injectAppTable` / `createAppColumnHelper`.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// app-table.ts
|
|
77
|
+
import {
|
|
78
|
+
createTableHook,
|
|
79
|
+
tableFeatures,
|
|
80
|
+
rowSortingFeature,
|
|
81
|
+
rowPaginationFeature,
|
|
82
|
+
createSortedRowModel,
|
|
83
|
+
createPaginatedRowModel,
|
|
84
|
+
sortFns,
|
|
85
|
+
} from '@tanstack/angular-table'
|
|
86
|
+
|
|
87
|
+
export const {
|
|
88
|
+
injectAppTable,
|
|
89
|
+
createAppColumnHelper,
|
|
90
|
+
injectTableContext,
|
|
91
|
+
injectTableCellContext,
|
|
92
|
+
injectTableHeaderContext,
|
|
93
|
+
} = createTableHook({
|
|
94
|
+
_features: tableFeatures({ rowSortingFeature, rowPaginationFeature }),
|
|
95
|
+
_rowModels: {
|
|
96
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
97
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
98
|
+
},
|
|
99
|
+
getRowId: (row) => row.id,
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Then in components:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { injectAppTable, createAppColumnHelper } from './app-table'
|
|
107
|
+
|
|
108
|
+
const columnHelper = createAppColumnHelper<Person>() // only TData generic needed
|
|
109
|
+
|
|
110
|
+
readonly table = injectAppTable(() => ({
|
|
111
|
+
columns,
|
|
112
|
+
data: this.data(),
|
|
113
|
+
})) // _features & _rowModels inherited
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`createTableHook` also lets you register `tableComponents` / `cellComponents` /
|
|
117
|
+
`headerComponents` registries — see `angular-rendering-directives` for that.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Lower-severity failure modes (MEDIUM/LOW)
|
|
122
|
+
|
|
123
|
+
### Using `state.x` and `atoms.x` together for the same slice
|
|
124
|
+
|
|
125
|
+
External atom silently wins. If you intended Angular-signal ownership, remove
|
|
126
|
+
`atoms.x` (or vice versa). Pick exactly one source of truth per slice.
|
|
127
|
+
|
|
128
|
+
### Forgetting the updater function form in `on[State]Change`
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// ❌ Crashes when TanStack Table passes a function
|
|
132
|
+
onSortingChange: (value) => this.sorting.set(value)
|
|
133
|
+
|
|
134
|
+
// ✅
|
|
135
|
+
onSortingChange: (updater) => {
|
|
136
|
+
updater instanceof Function
|
|
137
|
+
? this.sorting.update(updater)
|
|
138
|
+
: this.sorting.set(updater)
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Mutating `state` slice values in place
|
|
143
|
+
|
|
144
|
+
`state: { sorting: this.sorting() }` snapshots the current value. Mutating the
|
|
145
|
+
underlying signal value with `.push()` won't trigger re-evaluation. Always
|
|
146
|
+
produce a new reference: `this.sorting.update(s => [...s, newSort])`.
|
|
147
|
+
|
|
148
|
+
### Premature `computed` selector wrapping for a single read
|
|
149
|
+
|
|
150
|
+
For a single atom read in a template (`{{ table.atoms.pagination.get().pageIndex }}`),
|
|
151
|
+
a wrapper `computed` adds no value. Reach for `computed` only for derivation,
|
|
152
|
+
shared selectors, or `{ equal: shallow }`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { InjectionToken, inject } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
export const FlexRenderComponentProps = new InjectionToken<
|
|
4
|
+
NonNullable<unknown>
|
|
5
|
+
>('[@tanstack/angular-table] Flex render component context props')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Inject the flex render context props.
|
|
9
|
+
*
|
|
10
|
+
* Can be used in components rendered via FlexRender directives.
|
|
11
|
+
*/
|
|
12
|
+
export function injectFlexRenderContext<T extends NonNullable<unknown>>(): T {
|
|
13
|
+
return inject<T>(FlexRenderComponentProps)
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flags used to manage and optimize the rendering lifecycle of the content of the cell
|
|
3
|
+
* while using {@link FlexViewRenderer}.
|
|
4
|
+
*/
|
|
5
|
+
export const FlexRenderFlags = {
|
|
6
|
+
/**
|
|
7
|
+
* Indicates that the view is being created for the first time or will be cleared during the next update phase.
|
|
8
|
+
* This is the initial state and will transition after the first ngDoCheck.
|
|
9
|
+
*/
|
|
10
|
+
ViewFirstRender: 1 << 0,
|
|
11
|
+
/**
|
|
12
|
+
* Indicates the `content` property has been modified or the view requires a complete re-render.
|
|
13
|
+
* When this flag is enabled, the view will be cleared and recreated from scratch.
|
|
14
|
+
*/
|
|
15
|
+
ContentChanged: 1 << 1,
|
|
16
|
+
/**
|
|
17
|
+
* Indicates that the `props` property reference has changed.
|
|
18
|
+
* When this flag is enabled, the view context is updated based on the type of the content.
|
|
19
|
+
*
|
|
20
|
+
* For Component view, inputs will be updated and view will be marked as dirty.
|
|
21
|
+
* For TemplateRef and primitive values, view will be marked as dirty
|
|
22
|
+
*/
|
|
23
|
+
PropsReferenceChanged: 1 << 2,
|
|
24
|
+
/**
|
|
25
|
+
* Indicates that the current rendered view needs to be checked for changes.
|
|
26
|
+
* This will be set to true when `content(props)` result has changed or during
|
|
27
|
+
* forced update
|
|
28
|
+
*/
|
|
29
|
+
Dirty: 1 << 3,
|
|
30
|
+
/**
|
|
31
|
+
* Indicates that the first render effect has been checked at least one time.
|
|
32
|
+
*/
|
|
33
|
+
RenderEffectChecked: 1 << 4,
|
|
34
|
+
} as const
|