@tanstack/angular-table 9.0.0-alpha.9 → 9.0.0-beta.2

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 (54) 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 +1337 -239
  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 +212 -0
  40. package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
  41. package/src/reactivity.ts +105 -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 -148
  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 -30
  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/flex-render.ts +0 -184
  54. package/src/proxy.ts +0 -97
@@ -0,0 +1,397 @@
1
+ ---
2
+ name: angular/compose-with-tanstack-store
3
+ description: >
4
+ Compose TanStack Table v9 with `@tanstack/angular-store`. TanStack Table v9 is itself built on
5
+ TanStack Store — each state slice is an atom. Read surfaces include `table.atoms.<slice>`
6
+ (per-slice readonly, signal-backed), `table.state` / `table.store` (flat readonly views), and `table.baseAtoms.<slice>`
7
+ (writable). The `atoms` table option lets you replace an internal slice with an external
8
+ TanStack Store atom for cross-app sharing (URL sync, persistence, multi-table coordination).
9
+ In Angular, native signals + `state` + `on[State]Change` is the default; reach for external
10
+ atoms only when ownership crosses an app boundary the signal model can't easily span.
11
+ type: composition
12
+ library: tanstack-table
13
+ framework: angular
14
+ library_version: '9.0.0-alpha.48'
15
+ requires:
16
+ - angular/table-state
17
+ - state-management
18
+ sources:
19
+ - TanStack/table:docs/framework/angular/guide/table-state.md
20
+ - TanStack/table:docs/framework/angular/angular-table.md
21
+ - TanStack/table:packages/angular-table/src/reactivity.ts
22
+ - TanStack/table:packages/angular-table/src/injectTable.ts
23
+ - TanStack/store:packages/angular-store/src/
24
+ ---
25
+
26
+ # Compose with TanStack Store (Angular)
27
+
28
+ > TanStack Table v9 **is** a TanStack Store consumer. The internal state model
29
+ > uses `alien-signals` atoms, exposed as `table.atoms.<slice>`,
30
+ > `table.baseAtoms.<slice>`, and the flat `table.store`. In Angular, those
31
+ > atoms are signal-backed via the `angularReactivity(injector)` binding.
32
+ >
33
+ > **For most Angular Table apps, native signals + `state` + `on[State]Change`
34
+ > is the right ownership model.** Reach for `@tanstack/angular-store` atoms
35
+ > when the slice must travel through code that doesn't share an injection
36
+ > scope with the table — URL sync, multi-table coordination, persistence
37
+ > layers, server caches, devtools.
38
+
39
+ ---
40
+
41
+ ## 1. The three read surfaces
42
+
43
+ Every TanStack Table instance exposes its state at three layers:
44
+
45
+ | Surface | Shape | Angular reactivity | Use when |
46
+ | ------------------------- | --------------------------------- | ------------------------------- | ----------------------------------------------- |
47
+ | `table.baseAtoms.<slice>` | Writable `Atom<T>` | Backed by an Angular `signal` | Direct internal writes; rare |
48
+ | `table.atoms.<slice>` | Readonly `Atom<T>` (derived) | Backed by an Angular `computed` | Reading or driving Angular reactivity per-slice |
49
+ | `table.state` | Readonly flat proxy | Reads from slice atoms | Full-state JSON/debug output |
50
+ | `table.store` | Readonly flat `Store<TableState>` | Backed by an Angular `computed` | Low-level flat store access; rare |
51
+
52
+ All three are populated only for **registered features** (`features`). All
53
+ three are signal-backed via `angularReactivity(injector)`:
54
+ `createReadonlyAtom` → Angular `computed`, `createWritableAtom` → Angular
55
+ `signal`, subscriptions bridged through `toObservable(computed(signal), {
56
+ injector })`.
57
+
58
+ Read them inside templates, `computed(...)`, or `effect(...)` and Angular
59
+ tracks the dependency.
60
+
61
+ ```ts
62
+ this.table.atoms.pagination.get() // current value (reactive)
63
+ this.table.atoms.pagination.subscribe(obs) // RxJS observer form
64
+ this.table.state.pagination // flat proxy read; prefer atoms for narrow reads
65
+ this.table.baseAtoms.pagination.set(...) // direct internal write (avoid)
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 2. The `atoms` option — bring your own atom
71
+
72
+ The `atoms` table option lets you replace the internal `baseAtom` for a slice
73
+ with an **external TanStack Store atom**. Once registered, `table.atoms.<slice>`
74
+ reads from that external atom, and `table.set<X>` / `feature.on*Change` write
75
+ through it.
76
+
77
+ ```ts
78
+ import { Store } from '@tanstack/store'
79
+ import {
80
+ injectTable,
81
+ tableFeatures,
82
+ rowPaginationFeature,
83
+ rowSortingFeature,
84
+ type PaginationState,
85
+ type SortingState,
86
+ } from '@tanstack/angular-table'
87
+
88
+ const features = tableFeatures({ rowPaginationFeature, rowSortingFeature })
89
+
90
+ // Module-scope (or app-scope) shared atoms
91
+ export const paginationStore = new Store<PaginationState>({
92
+ pageIndex: 0,
93
+ pageSize: 25,
94
+ })
95
+
96
+ export const sortingStore = new Store<SortingState>([])
97
+
98
+ @Component({...})
99
+ export class App {
100
+ readonly table = injectTable(() => ({
101
+ features,
102
+ rowModels: { /* … */ },
103
+ columns,
104
+ data: this.data(),
105
+ atoms: {
106
+ pagination: paginationStore,
107
+ sorting: sortingStore,
108
+ },
109
+ }))
110
+ }
111
+ ```
112
+
113
+ Now `paginationStore.state` and `table.atoms.pagination.get()` always agree.
114
+ Any other consumer of `paginationStore` (a URL-sync service, another table,
115
+ a devtools panel) sees the same updates.
116
+
117
+ > **External atoms take precedence over `state.<slice>`.** If you supply both
118
+ > `atoms.pagination` and `state.pagination`, the atom wins silently.
119
+
120
+ ---
121
+
122
+ ## 3. When to use external atoms vs Angular signals
123
+
124
+ The maintainer guidance: **Angular signals first**. Atoms when ownership
125
+ crosses boundaries.
126
+
127
+ | Scenario | Use |
128
+ | ------------------------------------------------------------ | ---------------------------------------------------------------------------- |
129
+ | Single component owns the slice | Angular `signal()` + `state` + `on[State]Change` |
130
+ | URL-sync (deep-linkable pagination, filter, sort) | External `Store` atom — see §4 |
131
+ | Two tables share a `globalFilter` | External `Store` atom on a shared module |
132
+ | Persisting to `localStorage` between sessions | External `Store` atom + subscribe to write |
133
+ | TanStack Query owns the server cache, table consumes filters | External `Store` atom or Angular signal — both work; pick what reads cleaner |
134
+ | Devtools / inspector across multiple tables | External `Store` atom — uniform consumer surface |
135
+
136
+ In a pure component-local scenario, an Angular signal is simpler — fewer
137
+ imports, no module-level globals, plays nicely with `OnPush`.
138
+
139
+ ---
140
+
141
+ ## 4. Real example — URL-synced pagination
142
+
143
+ ```ts
144
+ import { effect, signal } from '@angular/core'
145
+ import { Router, ActivatedRoute } from '@angular/router'
146
+ import { Store } from '@tanstack/store'
147
+ import {
148
+ injectTable,
149
+ tableFeatures,
150
+ rowPaginationFeature,
151
+ type PaginationState,
152
+ } from '@tanstack/angular-table'
153
+
154
+ const features = tableFeatures({ rowPaginationFeature })
155
+
156
+ // Shared, app-scope atom
157
+ export const paginationStore = new Store<PaginationState>({
158
+ pageIndex: 0,
159
+ pageSize: 25,
160
+ })
161
+
162
+ @Component({
163
+ selector: 'page-route',
164
+ // …
165
+ })
166
+ export class PageRoute {
167
+ private readonly router = inject(Router)
168
+ private readonly route = inject(ActivatedRoute)
169
+
170
+ constructor() {
171
+ // Seed from URL once
172
+ const qp = this.route.snapshot.queryParamMap
173
+ paginationStore.setState((p) => ({
174
+ pageIndex: Number(qp.get('p') ?? p.pageIndex),
175
+ pageSize: Number(qp.get('s') ?? p.pageSize),
176
+ }))
177
+
178
+ // Mirror to URL
179
+ paginationStore.subscribe(() => {
180
+ const { pageIndex, pageSize } = paginationStore.state
181
+ this.router.navigate([], {
182
+ queryParams: { p: pageIndex, s: pageSize },
183
+ queryParamsHandling: 'merge',
184
+ replaceUrl: true,
185
+ })
186
+ })
187
+ }
188
+
189
+ readonly table = injectTable(() => ({
190
+ features,
191
+ rowModels: {
192
+ /* … */
193
+ },
194
+ columns,
195
+ data: this.data(),
196
+ atoms: {
197
+ pagination: paginationStore, // ← external atom owns the slice
198
+ },
199
+ }))
200
+ }
201
+ ```
202
+
203
+ `table.nextPage()` now writes to `paginationStore`, which writes to the URL.
204
+ A user copying the URL into a new tab lands on the same page.
205
+
206
+ ---
207
+
208
+ ## 5. Read external atom values reactively in Angular
209
+
210
+ `@tanstack/angular-store` provides `injectSelector` / `injectAtom` for
211
+ deriving Angular signals from TanStack Store atoms outside the table context:
212
+
213
+ ```ts
214
+ import { injectSelector } from '@tanstack/angular-store'
215
+ import { paginationStore } from './stores'
216
+
217
+ @Component({...})
218
+ export class StatsBar {
219
+ readonly pageIndex = injectSelector(paginationStore, (s) => s.pageIndex)
220
+ // Angular Signal<number>, signal-tracked normally
221
+ }
222
+ ```
223
+
224
+ Inside a table-owning component, you already have `table.atoms.pagination.get()`
225
+ — both forms are equivalent because the external atom _is_ the internal atom
226
+ for that slice.
227
+
228
+ ---
229
+
230
+ ## 6. Multi-table coordination
231
+
232
+ A common pattern: two tables on the same page should share a `globalFilter`.
233
+
234
+ ```ts
235
+ import { Store } from '@tanstack/store'
236
+
237
+ export const sharedFilter = new Store<string | null>(null)
238
+
239
+ // Table A
240
+ readonly tableA = injectTable(() => ({
241
+ features: tableFeatures({ globalFilteringFeature }),
242
+ rowModels: { filteredRowModel: createFilteredRowModel(filterFns) },
243
+ columns: columnsA,
244
+ data: this.dataA(),
245
+ atoms: { globalFilter: sharedFilter },
246
+ }))
247
+
248
+ // Table B (separate component, same module)
249
+ readonly tableB = injectTable(() => ({
250
+ features: tableFeatures({ globalFilteringFeature }),
251
+ rowModels: { filteredRowModel: createFilteredRowModel(filterFns) },
252
+ columns: columnsB,
253
+ data: this.dataB(),
254
+ atoms: { globalFilter: sharedFilter },
255
+ }))
256
+ ```
257
+
258
+ Calling `tableA.setGlobalFilter('foo')` updates `sharedFilter`, which Table B
259
+ also reads — both views filter together. Without the shared atom you'd need
260
+ a separate cross-component sync mechanism.
261
+
262
+ ---
263
+
264
+ ## 7. Reset semantics
265
+
266
+ This is a known sharp edge worth understanding:
267
+
268
+ - `table.resetPagination()` (and equivalents) updates _through the feature
269
+ state updater_. When the slice is owned by an external atom, the external
270
+ atom is updated.
271
+ - `table.reset()` (the core API) resets the **internal `baseAtoms`**. Do not
272
+ use it as the primary reset for externally-owned slices — it bypasses your
273
+ external atom.
274
+ - For an externally-owned slice, reset by writing to your atom directly
275
+ (`paginationStore.setState({ pageIndex: 0, pageSize: 25 })`) or by calling
276
+ the slice-specific reset API which routes through the updater.
277
+
278
+ ---
279
+
280
+ ## 8. State and atom precedence — the rules
281
+
282
+ For any given registered slice, the table picks state from this priority order:
283
+
284
+ 1. **`atoms.<slice>`** (external atom) — wins everything
285
+ 2. **`state.<slice>`** (controlled value, in initializer)
286
+ 3. **`initialState.<slice>`** (one-time seed)
287
+ 4. Feature default (slice's blank value)
288
+
289
+ **Don't supply more than one source for the same slice** unless you
290
+ intentionally want a specific layer to win. The precedence is silent — no
291
+ runtime warning today.
292
+
293
+ ---
294
+
295
+ ## 9. Cross-app patterns
296
+
297
+ ### Hydration / SSR-like state seeding
298
+
299
+ For SSR-rendered Angular tables that hydrate a known initial state from the
300
+ server payload, the atom approach is the cleanest:
301
+
302
+ ```ts
303
+ const paginationStore = new Store<PaginationState>(serverPayload.pagination)
304
+ // later:
305
+ atoms: {
306
+ pagination: paginationStore
307
+ }
308
+ ```
309
+
310
+ The atom is constructed with the hydrated value; the table never re-seeds
311
+ from `initialState` because `atoms` takes precedence.
312
+
313
+ ### Devtools / inspector
314
+
315
+ A separate devtools component can subscribe to `paginationStore`,
316
+ `sortingStore`, etc. without holding a reference to the table — useful when
317
+ the devtools live in a different injection scope.
318
+
319
+ ---
320
+
321
+ ## Failure modes
322
+
323
+ ### 1. (CRITICAL) Reimplementing TanStack Store with raw signals or Subjects
324
+
325
+ ```ts
326
+ // ❌ A homegrown shared store
327
+ @Injectable({ providedIn: 'root' })
328
+ export class FilterService {
329
+ readonly value = signal<string | null>(null)
330
+ setFilter(v: string | null) { this.value.set(v) }
331
+ }
332
+
333
+ // then trying to pipe it into the table…
334
+ state: { globalFilter: this.filterService.value() }
335
+ onGlobalFilterChange: (u) => /* ... */
336
+ ```
337
+
338
+ This works, but you've also lost the atom-bridge that lets `table.setGlobalFilter`
339
+ write back through. The atom flow (`atoms: { globalFilter: filterStore }`) is
340
+ fewer moving parts and idiomatic v9. Prefer it when the slice spans multiple
341
+ consumers.
342
+
343
+ ### 2. (CRITICAL) Supplying both `state.x` and `atoms.x` for the same slice
344
+
345
+ The atom wins; the Angular signal becomes a write-only ghost. No runtime
346
+ warning today. Pick one source of truth per slice. The most common bug here is
347
+ "I added atoms.pagination but the on[State]Change handler I left from before
348
+ no longer fires" — it does fire (the atom is updated through the table's
349
+ updater plumbing), but your Angular signal isn't being read by the table.
350
+
351
+ ### 3. (CRITICAL) Using `table.baseAtoms.x.set(...)` to update an externally-owned slice
352
+
353
+ `baseAtoms` are the internal writable atoms. When `atoms.x` is registered,
354
+ `table.atoms.x` and the feature updater route through the external atom, but
355
+ the internal `baseAtoms.x` is now an orphan — writing to it has no effect on
356
+ the table's behavior. Write to the external atom instead.
357
+
358
+ ### 4. (HIGH) Calling `table.reset()` on an externally-owned slice
359
+
360
+ `table.reset()` resets the internal `baseAtoms` — bypasses your external
361
+ atom. Use slice-specific resets (`resetSorting()`, `resetPagination()`,
362
+ `resetGlobalFilter()`) or write to the external atom directly.
363
+
364
+ ### 5. (HIGH) Forgetting that external atom state is reactive in Angular
365
+
366
+ Reading `paginationStore.state` in a template **is reactive** in v9 because
367
+ the adapter wraps it — but reading it in plain TypeScript outside a reactive
368
+ scope is a snapshot. Use `injectSelector(paginationStore, …)` to get an
369
+ Angular signal for general consumption, or read `table.atoms.pagination.get()`
370
+ inside the component that owns the table.
371
+
372
+ ### 6. (MEDIUM) Putting `new Store(...)` inside a component class field
373
+
374
+ Module-level (or `providedIn: 'root'` service) is the right place. Creating a
375
+ `new Store(...)` in a component field gives you a per-instance atom — which
376
+ defeats the cross-app sharing point. Use external atoms specifically for
377
+ _shared_ state.
378
+
379
+ ### 7. (MEDIUM) Hand-rolling subscription cleanup
380
+
381
+ When you `paginationStore.subscribe(fn)`, that returns an unsubscribe. Inside
382
+ an Angular component, prefer Angular's `effect(...) +
383
+ injectSelector(paginationStore, …)` for derived signals, or
384
+ `DestroyRef.onDestroy(unsubscribe)` for raw subscriptions.
385
+
386
+ ---
387
+
388
+ ## See also
389
+
390
+ - `tanstack-table/angular/table-state` — the prerequisite atom model
391
+ - `tanstack-table/angular/client-to-server` — server-driven tables (where
392
+ external atoms shine for URL sync)
393
+ - `tanstack-table/angular/compose-with-tanstack-query` — Query + Table
394
+ (sometimes external atoms simplify the bridge)
395
+ - `tanstack-table/core/state-management` — framework-agnostic atom semantics
396
+ - `@tanstack/angular-store` docs — `injectSelector`, `injectAtom`,
397
+ `createStoreContext`