@tanstack/angular-table 9.0.0-alpha.9 → 9.0.0-beta.10
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 +1299 -248
- 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 +787 -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 +468 -0
- package/skills/angular/compose-with-tanstack-query/SKILL.md +486 -0
- package/skills/angular/compose-with-tanstack-store/SKILL.md +405 -0
- package/skills/angular/compose-with-tanstack-virtual/SKILL.md +399 -0
- package/skills/angular/getting-started/SKILL.md +487 -0
- package/skills/angular/getting-started/references/feature-row-model-mapping.md +51 -0
- package/skills/angular/migrate-v8-to-v9/SKILL.md +422 -0
- package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +268 -0
- package/skills/angular/production-readiness/SKILL.md +472 -0
- package/skills/angular/table-state/SKILL.md +422 -0
- package/skills/angular/table-state/references/external-atoms-and-app-hook.md +153 -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 +108 -0
- package/src/helpers/createTableHook.ts +498 -0
- package/src/helpers/flexRenderCell.ts +136 -0
- package/src/helpers/header.ts +101 -0
- package/src/helpers/table.ts +87 -0
- package/src/index.ts +23 -70
- package/src/injectTable.ts +146 -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,405 @@
|
|
|
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
|
+
columns,
|
|
103
|
+
data: this.data(),
|
|
104
|
+
atoms: {
|
|
105
|
+
pagination: paginationStore,
|
|
106
|
+
sorting: sortingStore,
|
|
107
|
+
},
|
|
108
|
+
}))
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Now `paginationStore.state` and `table.atoms.pagination.get()` always agree.
|
|
113
|
+
Any other consumer of `paginationStore` (a URL-sync service, another table,
|
|
114
|
+
a devtools panel) sees the same updates.
|
|
115
|
+
|
|
116
|
+
> **External atoms take precedence over `state.<slice>`.** If you supply both
|
|
117
|
+
> `atoms.pagination` and `state.pagination`, the atom wins silently.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 3. When to use external atoms vs Angular signals
|
|
122
|
+
|
|
123
|
+
The maintainer guidance: **Angular signals first**. Atoms when ownership
|
|
124
|
+
crosses boundaries.
|
|
125
|
+
|
|
126
|
+
| Scenario | Use |
|
|
127
|
+
| ------------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
128
|
+
| Single component owns the slice | Angular `signal()` + `state` + `on[State]Change` |
|
|
129
|
+
| URL-sync (deep-linkable pagination, filter, sort) | External `Store` atom — see §4 |
|
|
130
|
+
| Two tables share a `globalFilter` | External `Store` atom on a shared module |
|
|
131
|
+
| Persisting to `localStorage` between sessions | External `Store` atom + subscribe to write |
|
|
132
|
+
| TanStack Query owns the server cache, table consumes filters | External `Store` atom or Angular signal — both work; pick what reads cleaner |
|
|
133
|
+
| Devtools / inspector across multiple tables | External `Store` atom — uniform consumer surface |
|
|
134
|
+
|
|
135
|
+
In a pure component-local scenario, an Angular signal is simpler — fewer
|
|
136
|
+
imports, no module-level globals, plays nicely with `OnPush`.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 4. Real example — URL-synced pagination
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { effect, signal } from '@angular/core'
|
|
144
|
+
import { Router, ActivatedRoute } from '@angular/router'
|
|
145
|
+
import { Store } from '@tanstack/store'
|
|
146
|
+
import {
|
|
147
|
+
injectTable,
|
|
148
|
+
tableFeatures,
|
|
149
|
+
rowPaginationFeature,
|
|
150
|
+
type PaginationState,
|
|
151
|
+
} from '@tanstack/angular-table'
|
|
152
|
+
|
|
153
|
+
const features = tableFeatures({ rowPaginationFeature })
|
|
154
|
+
|
|
155
|
+
// Shared, app-scope atom
|
|
156
|
+
export const paginationStore = new Store<PaginationState>({
|
|
157
|
+
pageIndex: 0,
|
|
158
|
+
pageSize: 25,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
@Component({
|
|
162
|
+
selector: 'page-route',
|
|
163
|
+
// …
|
|
164
|
+
})
|
|
165
|
+
export class PageRoute {
|
|
166
|
+
private readonly router = inject(Router)
|
|
167
|
+
private readonly route = inject(ActivatedRoute)
|
|
168
|
+
|
|
169
|
+
constructor() {
|
|
170
|
+
// Seed from URL once
|
|
171
|
+
const qp = this.route.snapshot.queryParamMap
|
|
172
|
+
paginationStore.setState((p) => ({
|
|
173
|
+
pageIndex: Number(qp.get('p') ?? p.pageIndex),
|
|
174
|
+
pageSize: Number(qp.get('s') ?? p.pageSize),
|
|
175
|
+
}))
|
|
176
|
+
|
|
177
|
+
// Mirror to URL
|
|
178
|
+
paginationStore.subscribe(() => {
|
|
179
|
+
const { pageIndex, pageSize } = paginationStore.state
|
|
180
|
+
this.router.navigate([], {
|
|
181
|
+
queryParams: { p: pageIndex, s: pageSize },
|
|
182
|
+
queryParamsHandling: 'merge',
|
|
183
|
+
replaceUrl: true,
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
readonly table = injectTable(() => ({
|
|
189
|
+
features,
|
|
190
|
+
columns,
|
|
191
|
+
data: this.data(),
|
|
192
|
+
atoms: {
|
|
193
|
+
pagination: paginationStore, // ← external atom owns the slice
|
|
194
|
+
},
|
|
195
|
+
}))
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`table.nextPage()` now writes to `paginationStore`, which writes to the URL.
|
|
200
|
+
A user copying the URL into a new tab lands on the same page.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 5. Read external atom values reactively in Angular
|
|
205
|
+
|
|
206
|
+
`@tanstack/angular-store` provides `injectSelector` / `injectAtom` for
|
|
207
|
+
deriving Angular signals from TanStack Store atoms outside the table context:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { injectSelector } from '@tanstack/angular-store'
|
|
211
|
+
import { paginationStore } from './stores'
|
|
212
|
+
|
|
213
|
+
@Component({...})
|
|
214
|
+
export class StatsBar {
|
|
215
|
+
readonly pageIndex = injectSelector(paginationStore, (s) => s.pageIndex)
|
|
216
|
+
// Angular Signal<number>, signal-tracked normally
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Inside a table-owning component, you already have `table.atoms.pagination.get()`
|
|
221
|
+
— both forms are equivalent because the external atom _is_ the internal atom
|
|
222
|
+
for that slice.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 6. Multi-table coordination
|
|
227
|
+
|
|
228
|
+
A common pattern: two tables on the same page should share a `globalFilter`.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { Store } from '@tanstack/store'
|
|
232
|
+
|
|
233
|
+
export const sharedFilter = new Store<string | null>(null)
|
|
234
|
+
|
|
235
|
+
const featuresA = tableFeatures({
|
|
236
|
+
columnFilteringFeature,
|
|
237
|
+
globalFilteringFeature,
|
|
238
|
+
filteredRowModel: createFilteredRowModel(),
|
|
239
|
+
filterFns,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const featuresB = tableFeatures({
|
|
243
|
+
columnFilteringFeature,
|
|
244
|
+
globalFilteringFeature,
|
|
245
|
+
filteredRowModel: createFilteredRowModel(),
|
|
246
|
+
filterFns,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// Table A
|
|
250
|
+
readonly tableA = injectTable(() => ({
|
|
251
|
+
features: featuresA,
|
|
252
|
+
columns: columnsA,
|
|
253
|
+
data: this.dataA(),
|
|
254
|
+
atoms: { globalFilter: sharedFilter },
|
|
255
|
+
}))
|
|
256
|
+
|
|
257
|
+
// Table B (separate component, same module)
|
|
258
|
+
readonly tableB = injectTable(() => ({
|
|
259
|
+
features: featuresB,
|
|
260
|
+
columns: columnsB,
|
|
261
|
+
data: this.dataB(),
|
|
262
|
+
atoms: { globalFilter: sharedFilter },
|
|
263
|
+
}))
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Calling `tableA.setGlobalFilter('foo')` updates `sharedFilter`, which Table B
|
|
267
|
+
also reads — both views filter together. Without the shared atom you'd need
|
|
268
|
+
a separate cross-component sync mechanism.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 7. Reset semantics
|
|
273
|
+
|
|
274
|
+
This is a known sharp edge worth understanding:
|
|
275
|
+
|
|
276
|
+
- `table.resetPagination()` (and equivalents) updates _through the feature
|
|
277
|
+
state updater_. When the slice is owned by an external atom, the external
|
|
278
|
+
atom is updated.
|
|
279
|
+
- `table.reset()` (the core API) resets the **internal `baseAtoms`**. Do not
|
|
280
|
+
use it as the primary reset for externally-owned slices — it bypasses your
|
|
281
|
+
external atom.
|
|
282
|
+
- For an externally-owned slice, reset by writing to your atom directly
|
|
283
|
+
(`paginationStore.setState({ pageIndex: 0, pageSize: 25 })`) or by calling
|
|
284
|
+
the slice-specific reset API which routes through the updater.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 8. State and atom precedence — the rules
|
|
289
|
+
|
|
290
|
+
For any given registered slice, the table picks state from this priority order:
|
|
291
|
+
|
|
292
|
+
1. **`atoms.<slice>`** (external atom) — wins everything
|
|
293
|
+
2. **`state.<slice>`** (controlled value, in initializer)
|
|
294
|
+
3. **`initialState.<slice>`** (one-time seed)
|
|
295
|
+
4. Feature default (slice's blank value)
|
|
296
|
+
|
|
297
|
+
**Don't supply more than one source for the same slice** unless you
|
|
298
|
+
intentionally want a specific layer to win. The precedence is silent — no
|
|
299
|
+
runtime warning today.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 9. Cross-app patterns
|
|
304
|
+
|
|
305
|
+
### Hydration / SSR-like state seeding
|
|
306
|
+
|
|
307
|
+
For SSR-rendered Angular tables that hydrate a known initial state from the
|
|
308
|
+
server payload, the atom approach is the cleanest:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const paginationStore = new Store<PaginationState>(serverPayload.pagination)
|
|
312
|
+
// later:
|
|
313
|
+
atoms: {
|
|
314
|
+
pagination: paginationStore
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The atom is constructed with the hydrated value; the table never re-seeds
|
|
319
|
+
from `initialState` because `atoms` takes precedence.
|
|
320
|
+
|
|
321
|
+
### Devtools / inspector
|
|
322
|
+
|
|
323
|
+
A separate devtools component can subscribe to `paginationStore`,
|
|
324
|
+
`sortingStore`, etc. without holding a reference to the table — useful when
|
|
325
|
+
the devtools live in a different injection scope.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Failure modes
|
|
330
|
+
|
|
331
|
+
### 1. (CRITICAL) Reimplementing TanStack Store with raw signals or Subjects
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
// ❌ A homegrown shared store
|
|
335
|
+
@Injectable({ providedIn: 'root' })
|
|
336
|
+
export class FilterService {
|
|
337
|
+
readonly value = signal<string | null>(null)
|
|
338
|
+
setFilter(v: string | null) { this.value.set(v) }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// then trying to pipe it into the table…
|
|
342
|
+
state: { globalFilter: this.filterService.value() }
|
|
343
|
+
onGlobalFilterChange: (u) => /* ... */
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
This works, but you've also lost the atom-bridge that lets `table.setGlobalFilter`
|
|
347
|
+
write back through. The atom flow (`atoms: { globalFilter: filterStore }`) is
|
|
348
|
+
fewer moving parts and idiomatic v9. Prefer it when the slice spans multiple
|
|
349
|
+
consumers.
|
|
350
|
+
|
|
351
|
+
### 2. (CRITICAL) Supplying both `state.x` and `atoms.x` for the same slice
|
|
352
|
+
|
|
353
|
+
The atom wins; the Angular signal becomes a write-only ghost. No runtime
|
|
354
|
+
warning today. Pick one source of truth per slice. The most common bug here is
|
|
355
|
+
"I added atoms.pagination but the on[State]Change handler I left from before
|
|
356
|
+
no longer fires" — it does fire (the atom is updated through the table's
|
|
357
|
+
updater plumbing), but your Angular signal isn't being read by the table.
|
|
358
|
+
|
|
359
|
+
### 3. (CRITICAL) Using `table.baseAtoms.x.set(...)` to update an externally-owned slice
|
|
360
|
+
|
|
361
|
+
`baseAtoms` are the internal writable atoms. When `atoms.x` is registered,
|
|
362
|
+
`table.atoms.x` and the feature updater route through the external atom, but
|
|
363
|
+
the internal `baseAtoms.x` is now an orphan — writing to it has no effect on
|
|
364
|
+
the table's behavior. Write to the external atom instead.
|
|
365
|
+
|
|
366
|
+
### 4. (HIGH) Calling `table.reset()` on an externally-owned slice
|
|
367
|
+
|
|
368
|
+
`table.reset()` resets the internal `baseAtoms` — bypasses your external
|
|
369
|
+
atom. Use slice-specific resets (`resetSorting()`, `resetPagination()`,
|
|
370
|
+
`resetGlobalFilter()`) or write to the external atom directly.
|
|
371
|
+
|
|
372
|
+
### 5. (HIGH) Forgetting that external atom state is reactive in Angular
|
|
373
|
+
|
|
374
|
+
Reading `paginationStore.state` in a template **is reactive** in v9 because
|
|
375
|
+
the adapter wraps it — but reading it in plain TypeScript outside a reactive
|
|
376
|
+
scope is a snapshot. Use `injectSelector(paginationStore, …)` to get an
|
|
377
|
+
Angular signal for general consumption, or read `table.atoms.pagination.get()`
|
|
378
|
+
inside the component that owns the table.
|
|
379
|
+
|
|
380
|
+
### 6. (MEDIUM) Putting `new Store(...)` inside a component class field
|
|
381
|
+
|
|
382
|
+
Module-level (or `providedIn: 'root'` service) is the right place. Creating a
|
|
383
|
+
`new Store(...)` in a component field gives you a per-instance atom — which
|
|
384
|
+
defeats the cross-app sharing point. Use external atoms specifically for
|
|
385
|
+
_shared_ state.
|
|
386
|
+
|
|
387
|
+
### 7. (MEDIUM) Hand-rolling subscription cleanup
|
|
388
|
+
|
|
389
|
+
When you `paginationStore.subscribe(fn)`, that returns an unsubscribe. Inside
|
|
390
|
+
an Angular component, prefer Angular's `effect(...) +
|
|
391
|
+
injectSelector(paginationStore, …)` for derived signals, or
|
|
392
|
+
`DestroyRef.onDestroy(unsubscribe)` for raw subscriptions.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## See also
|
|
397
|
+
|
|
398
|
+
- `tanstack-table/angular/table-state` — the prerequisite atom model
|
|
399
|
+
- `tanstack-table/angular/client-to-server` — server-driven tables (where
|
|
400
|
+
external atoms shine for URL sync)
|
|
401
|
+
- `tanstack-table/angular/compose-with-tanstack-query` — Query + Table
|
|
402
|
+
(sometimes external atoms simplify the bridge)
|
|
403
|
+
- `tanstack-table/core/state-management` — framework-agnostic atom semantics
|
|
404
|
+
- `@tanstack/angular-store` docs — `injectSelector`, `injectAtom`,
|
|
405
|
+
`createStoreContext`
|