@tanstack/angular-table 9.0.0-alpha.47 → 9.0.0-alpha.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/README.md +10 -0
- package/dist/fesm2022/tanstack-angular-table.mjs +18 -18
- package/package.json +8 -6
- 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 +396 -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 +415 -0
- package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +261 -0
- package/skills/angular/production-readiness/SKILL.md +468 -0
- package/skills/angular/table-state/SKILL.md +425 -0
- package/skills/angular/table-state/references/external-atoms-and-app-hook.md +152 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular/client-to-server
|
|
3
|
+
description: >
|
|
4
|
+
Convert an Angular Table v9 from client-side to server-side processing. Flip
|
|
5
|
+
`manualPagination` / `manualSorting` / `manualFiltering` / `manualGrouping` / `manualExpanding`
|
|
6
|
+
for the slices the server now owns; drop the corresponding `_rowModels` row-model factories the
|
|
7
|
+
server replaces; supply `rowCount` (server total) so pagination computes correctly; hoist
|
|
8
|
+
`pagination` / `sorting` / `columnFilters` / `globalFilter` to Angular signals with `state` +
|
|
9
|
+
`on[State]Change`; fetch via `rxResource` / `httpResource` / `@tanstack/angular-query`; preserve
|
|
10
|
+
previous data on refetch with `linkedSignal` (or `placeholderData: keepPreviousData` for Query);
|
|
11
|
+
set `getRowId` for stable selection across refetches.
|
|
12
|
+
type: lifecycle
|
|
13
|
+
library: tanstack-table
|
|
14
|
+
framework: angular
|
|
15
|
+
library_version: '9.0.0-alpha.48'
|
|
16
|
+
requires:
|
|
17
|
+
- angular/table-state
|
|
18
|
+
- angular/getting-started
|
|
19
|
+
- filtering
|
|
20
|
+
- sorting
|
|
21
|
+
- pagination
|
|
22
|
+
sources:
|
|
23
|
+
- TanStack/table:docs/framework/angular/guide/table-state.md
|
|
24
|
+
- TanStack/table:docs/framework/angular/guide/migrating.md
|
|
25
|
+
- TanStack/table:examples/angular/remote-data/
|
|
26
|
+
- TanStack/table:packages/angular-table/src/injectTable.ts
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Client → Server Conversion (Angular Table v9)
|
|
30
|
+
|
|
31
|
+
> Goal: take a working client-side Angular Table v9 and migrate it to server-driven
|
|
32
|
+
> processing for one or more of pagination / sorting / filtering / grouping /
|
|
33
|
+
> expanding — without rewriting your row markup, columns, or feature surface.
|
|
34
|
+
>
|
|
35
|
+
> The canonical Angular example is `examples/angular/remote-data/`, using
|
|
36
|
+
> `rxResource` + `linkedSignal`. The same pattern composes with
|
|
37
|
+
> `@tanstack/angular-query` (see `compose-with-tanstack-query`).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 1. The 5-step recipe
|
|
42
|
+
|
|
43
|
+
For each slice the server now owns:
|
|
44
|
+
|
|
45
|
+
1. **Flip `manualX: true`** in table options. This tells the table "don't
|
|
46
|
+
process this on the client — trust the data you receive."
|
|
47
|
+
2. **Drop the matching client-side row-model factory** from `_rowModels`
|
|
48
|
+
(or keep it if you still want the feature's _state_ but no client
|
|
49
|
+
recomputation — see §3).
|
|
50
|
+
3. **Hoist the slice to an Angular signal**, control it via `state.x` +
|
|
51
|
+
`on[State]Change`. Server requests must depend on the signal.
|
|
52
|
+
4. **Pass `rowCount`** (the server's total) so `getPageCount()`,
|
|
53
|
+
`getCanNextPage()`, etc. compute correctly under `manualPagination`.
|
|
54
|
+
5. **Set `getRowId`** so row selection (and refetch identity) survives across
|
|
55
|
+
server refetches.
|
|
56
|
+
|
|
57
|
+
Plus: keep previous data visible during refetches (avoid a "0-rows flash") with
|
|
58
|
+
`linkedSignal`, `placeholderData: keepPreviousData` (Query), or
|
|
59
|
+
`previousValue:` (`httpResource`).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 2. The `manualX` matrix
|
|
64
|
+
|
|
65
|
+
| Slice | Option | Client row-model needed? | Notes |
|
|
66
|
+
| -------------- | ------------------------ | ------------------------ | -------------------------------------------------------------- |
|
|
67
|
+
| Pagination | `manualPagination: true` | drop `paginatedRowModel` | also pass `rowCount: <serverTotal>` |
|
|
68
|
+
| Sorting | `manualSorting: true` | drop `sortedRowModel` | feature still controls `sorting` state |
|
|
69
|
+
| Column filters | `manualFiltering: true` | drop `filteredRowModel` | also affects global filter when sharing the filtered row model |
|
|
70
|
+
| Global filter | `manualFiltering: true` | drop `filteredRowModel` | global filter shares the filtered row model |
|
|
71
|
+
| Grouping | `manualGrouping: true` | drop `groupedRowModel` | rare — most servers don't return grouped trees |
|
|
72
|
+
| Expanding | `manualExpanding: true` | drop `expandedRowModel` | server returns sub-rows pre-expanded |
|
|
73
|
+
|
|
74
|
+
Selection, visibility, ordering, pinning, sizing, resizing, row-pinning are
|
|
75
|
+
all UI-only state — they don't have manual modes. They keep working unchanged.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 3. Canonical example — pagination + sorting + global filter
|
|
80
|
+
|
|
81
|
+
The `examples/angular/remote-data/` pattern, condensed:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import {
|
|
85
|
+
ChangeDetectionStrategy,
|
|
86
|
+
Component,
|
|
87
|
+
inject,
|
|
88
|
+
linkedSignal,
|
|
89
|
+
signal,
|
|
90
|
+
} from '@angular/core'
|
|
91
|
+
import { rxResource } from '@angular/core/rxjs-interop'
|
|
92
|
+
import { HttpClient, HttpParams } from '@angular/common/http'
|
|
93
|
+
import { map } from 'rxjs'
|
|
94
|
+
import {
|
|
95
|
+
FlexRender,
|
|
96
|
+
injectTable,
|
|
97
|
+
tableFeatures,
|
|
98
|
+
rowPaginationFeature,
|
|
99
|
+
rowSortingFeature,
|
|
100
|
+
globalFilteringFeature,
|
|
101
|
+
createColumnHelper,
|
|
102
|
+
type ColumnDef,
|
|
103
|
+
type PaginationState,
|
|
104
|
+
type SortingState,
|
|
105
|
+
} from '@tanstack/angular-table'
|
|
106
|
+
|
|
107
|
+
const _features = tableFeatures({
|
|
108
|
+
rowPaginationFeature,
|
|
109
|
+
rowSortingFeature,
|
|
110
|
+
globalFilteringFeature,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const columnHelper = createColumnHelper<typeof _features, Todo>()
|
|
114
|
+
|
|
115
|
+
type TodoResponse = { items: Array<Todo>; totalCount: number }
|
|
116
|
+
|
|
117
|
+
@Component({
|
|
118
|
+
selector: 'app-root',
|
|
119
|
+
imports: [FlexRender],
|
|
120
|
+
templateUrl: './app.html',
|
|
121
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
122
|
+
})
|
|
123
|
+
export class App {
|
|
124
|
+
private readonly http = inject(HttpClient)
|
|
125
|
+
|
|
126
|
+
// 1. Hoist controlled slices to signals
|
|
127
|
+
readonly pagination = signal<PaginationState>({ pageIndex: 0, pageSize: 10 })
|
|
128
|
+
readonly sorting = signal<SortingState>([{ id: 'id', desc: false }])
|
|
129
|
+
readonly globalFilter = signal<string | null>(null)
|
|
130
|
+
|
|
131
|
+
// 2. Fetch — server query depends on those signals
|
|
132
|
+
private readonly data = rxResource({
|
|
133
|
+
params: () => ({
|
|
134
|
+
page: this.pagination(),
|
|
135
|
+
sorting: this.sorting(),
|
|
136
|
+
globalFilter: this.globalFilter(),
|
|
137
|
+
}),
|
|
138
|
+
stream: ({ params: { page, sorting, globalFilter } }) => {
|
|
139
|
+
let params = new HttpParams({
|
|
140
|
+
fromObject: {
|
|
141
|
+
_page: page.pageIndex + 1,
|
|
142
|
+
_limit: page.pageSize,
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
if (globalFilter) params = params.set('title_like', globalFilter)
|
|
146
|
+
if (sorting.length) {
|
|
147
|
+
params = params
|
|
148
|
+
.set('_sort', sorting.map((s) => s.id).join(','))
|
|
149
|
+
.set(
|
|
150
|
+
'_order',
|
|
151
|
+
sorting.map((s) => (s.desc ? 'desc' : 'asc')).join(','),
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
return this.http
|
|
155
|
+
.get<Array<Todo>>('https://jsonplaceholder.typicode.com/todos', {
|
|
156
|
+
params,
|
|
157
|
+
observe: 'response',
|
|
158
|
+
})
|
|
159
|
+
.pipe(
|
|
160
|
+
map(
|
|
161
|
+
(res) =>
|
|
162
|
+
({
|
|
163
|
+
items: res.body ?? [],
|
|
164
|
+
totalCount: Number(res.headers.get('X-Total-Count')),
|
|
165
|
+
}) satisfies TodoResponse,
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// 3. Keep previous page visible during refetch — no "0 rows" flash
|
|
172
|
+
readonly dataWithLatest = linkedSignal<
|
|
173
|
+
{
|
|
174
|
+
value: TodoResponse | undefined
|
|
175
|
+
status: 'idle' | 'loading' | 'resolved' | 'error'
|
|
176
|
+
},
|
|
177
|
+
TodoResponse
|
|
178
|
+
>({
|
|
179
|
+
source: () => ({
|
|
180
|
+
value: this.data.value(),
|
|
181
|
+
status: this.data.status(),
|
|
182
|
+
}),
|
|
183
|
+
computation: (source, previous) => {
|
|
184
|
+
if (previous && source.status === 'loading') return previous.value
|
|
185
|
+
return source.value ?? { items: [], totalCount: 0 }
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
readonly columns: Array<ColumnDef<typeof _features, Todo>> = [
|
|
190
|
+
columnHelper.accessor('id', { header: 'Id', cell: (i) => i.getValue() }),
|
|
191
|
+
columnHelper.accessor('title', {
|
|
192
|
+
header: 'Title',
|
|
193
|
+
cell: (i) => i.getValue(),
|
|
194
|
+
}),
|
|
195
|
+
columnHelper.accessor('completed', {
|
|
196
|
+
header: 'Completed',
|
|
197
|
+
cell: (i) => (i.getValue() ? '✅' : '❌'),
|
|
198
|
+
}),
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
// 4. Wire the table — manualX flags, drop row-model factories, supply rowCount
|
|
202
|
+
readonly table = injectTable(() => {
|
|
203
|
+
const data = this.dataWithLatest()
|
|
204
|
+
return {
|
|
205
|
+
_features,
|
|
206
|
+
_rowModels: {}, // ← dropped paginatedRowModel, sortedRowModel, filteredRowModel
|
|
207
|
+
columns: this.columns,
|
|
208
|
+
data: data.items,
|
|
209
|
+
getRowId: (row) => String(row.id),
|
|
210
|
+
|
|
211
|
+
// Controlled slices
|
|
212
|
+
state: {
|
|
213
|
+
pagination: this.pagination(),
|
|
214
|
+
sorting: this.sorting(),
|
|
215
|
+
globalFilter: this.globalFilter(),
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
// Manual modes
|
|
219
|
+
manualPagination: true,
|
|
220
|
+
manualSorting: true,
|
|
221
|
+
manualFiltering: true,
|
|
222
|
+
|
|
223
|
+
// Server's truth about total row count
|
|
224
|
+
rowCount: data.totalCount,
|
|
225
|
+
|
|
226
|
+
onPaginationChange: (u) =>
|
|
227
|
+
typeof u === 'function'
|
|
228
|
+
? this.pagination.update(u)
|
|
229
|
+
: this.pagination.set(u),
|
|
230
|
+
|
|
231
|
+
onSortingChange: (u) =>
|
|
232
|
+
typeof u === 'function' ? this.sorting.update(u) : this.sorting.set(u),
|
|
233
|
+
|
|
234
|
+
// When filter changes, also reset page index
|
|
235
|
+
onGlobalFilterChange: (u) => {
|
|
236
|
+
typeof u === 'function'
|
|
237
|
+
? this.globalFilter.update(u)
|
|
238
|
+
: this.globalFilter.set(u)
|
|
239
|
+
this.pagination.update((p) => ({ ...p, pageIndex: 0 }))
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### What changed from the client-side version
|
|
247
|
+
|
|
248
|
+
- `_rowModels: {}` — no `paginatedRowModel`, no `sortedRowModel`, no
|
|
249
|
+
`filteredRowModel`. The server is the source of truth.
|
|
250
|
+
- `manualPagination` / `manualSorting` / `manualFiltering: true`.
|
|
251
|
+
- `rowCount: data.totalCount` — required for correct `getPageCount()` and the
|
|
252
|
+
next/prev buttons.
|
|
253
|
+
- `state` + per-slice `on[State]Change` for everything the server reads.
|
|
254
|
+
- `getRowId` set so row selection survives refetch reorderings.
|
|
255
|
+
- `linkedSignal` keeps the previous response visible during loading — without
|
|
256
|
+
it, paginating yields a one-frame "no rows" flash because `data.value()` is
|
|
257
|
+
`undefined` mid-fetch.
|
|
258
|
+
- Resetting `pageIndex` on global-filter change is a UX rule, not framework
|
|
259
|
+
behavior — make it explicit.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 4. Wiring with `@tanstack/angular-query-experimental`
|
|
264
|
+
|
|
265
|
+
For Query users, the equivalent of `linkedSignal` is
|
|
266
|
+
`placeholderData: keepPreviousData`. See `compose-with-tanstack-query` for the
|
|
267
|
+
full pattern. The table-side wiring (manual flags, dropped row models,
|
|
268
|
+
controlled signals, `rowCount`, `getRowId`) is identical.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 5. `rowCount` and friends
|
|
273
|
+
|
|
274
|
+
Under `manualPagination: true`, the table no longer knows the total. You must
|
|
275
|
+
tell it:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
rowCount: data.totalCount // total rows server reports
|
|
279
|
+
// pageCount: 42 // can be passed instead, if your API gives pages not rows
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
If you omit both, `getPageCount()` returns `-1` and the "next page" button
|
|
283
|
+
never disables. If your API reports `pageCount` directly (rare), prefer
|
|
284
|
+
`pageCount` — otherwise compute it from `rowCount`.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 6. Always set `getRowId` when server-driven
|
|
289
|
+
|
|
290
|
+
Without `getRowId`, row IDs default to row index. That works on the client
|
|
291
|
+
because order is stable per render. On the server, a refetch may return rows in
|
|
292
|
+
a different order — `RowSelectionState`, keyed by row ID, then targets the
|
|
293
|
+
wrong rows.
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
getRowId: (row) => row.id
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Required for:
|
|
300
|
+
|
|
301
|
+
- `rowSelectionFeature` correctness across refetches
|
|
302
|
+
- pinned-row identity
|
|
303
|
+
- stable `track row.id` performance in `@for`
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 7. Debouncing rapid input — global filter typing
|
|
308
|
+
|
|
309
|
+
Naively, every keystroke triggers a server fetch. Two options:
|
|
310
|
+
|
|
311
|
+
- **Manual signal indirection** — keep a `globalFilterInput` signal that the
|
|
312
|
+
UI writes to, then update `globalFilter` after a delay via `effect(...) +
|
|
313
|
+
setTimeout` or RxJS `debounceTime`.
|
|
314
|
+
- **Compose with `@tanstack/angular-pacer`** — see
|
|
315
|
+
`compose-with-tanstack-pacer` (not in this batch but on the roadmap).
|
|
316
|
+
|
|
317
|
+
Resetting `pageIndex` to 0 when filter or sort changes is a UX standard:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
onGlobalFilterChange: (u) => {
|
|
321
|
+
typeof u === 'function' ? this.globalFilter.update(u) : this.globalFilter.set(u)
|
|
322
|
+
this.pagination.update((p) => ({ ...p, pageIndex: 0 }))
|
|
323
|
+
},
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 8. Mixed mode — some slices server, others client
|
|
329
|
+
|
|
330
|
+
Common pattern: pagination + sorting on the server, but row selection +
|
|
331
|
+
column visibility stay client-only. **Nothing special required** — only the
|
|
332
|
+
slices you mark `manualX` are server-driven. Selection / visibility / ordering
|
|
333
|
+
work unchanged.
|
|
334
|
+
|
|
335
|
+
You can also keep client-side filtering on a column while paginating on the
|
|
336
|
+
server, but be wary: if rows are paginated server-side, you only have the
|
|
337
|
+
current page to filter against. Usually it's cleaner to flip all data-shape
|
|
338
|
+
slices to the server consistently.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 9. Resetting state on slice changes
|
|
343
|
+
|
|
344
|
+
These behaviors are intentional and you'll often want to _override_ them when
|
|
345
|
+
server-driven:
|
|
346
|
+
|
|
347
|
+
| Default | When server-driven |
|
|
348
|
+
| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
349
|
+
| `autoResetPageIndex: true` resets `pageIndex` to 0 when data identity changes | OK as-is — every fetch is a new array reference, so the page index resets unless you also pass `autoResetPageIndex: false` |
|
|
350
|
+
| Filter change does **not** auto-reset page | UX-standard to reset manually (see §7) |
|
|
351
|
+
| Sort change does **not** auto-reset page | Reset manually if your UX expects "new sort → page 1" |
|
|
352
|
+
|
|
353
|
+
If your fetch always returns a fresh array, **set `autoResetPageIndex: false`** —
|
|
354
|
+
otherwise paginating to page 3 will reset back to page 0 the moment the new
|
|
355
|
+
data lands. The remote-data example demonstrates the alternative pattern for
|
|
356
|
+
edits (toggle the flag around the update).
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Failure modes
|
|
361
|
+
|
|
362
|
+
### 1. (CRITICAL) Flipping `manualPagination: true` but keeping
|
|
363
|
+
|
|
364
|
+
`paginatedRowModel` in `_rowModels`
|
|
365
|
+
|
|
366
|
+
The client row-model factory will re-paginate the (already-paginated) data,
|
|
367
|
+
chopping the visible rows down to the first `pageSize` of the page slice.
|
|
368
|
+
**Drop the factory** when going manual — or accept double-pagination.
|
|
369
|
+
|
|
370
|
+
### 2. (CRITICAL) Forgetting `rowCount` under `manualPagination`
|
|
371
|
+
|
|
372
|
+
`getPageCount()` returns `-1`, `getCanNextPage()` is `true` forever,
|
|
373
|
+
"page N of -1" appears in the UI. Always pass either `rowCount: serverTotal`
|
|
374
|
+
or `pageCount: serverPageCount`.
|
|
375
|
+
|
|
376
|
+
### 3. (CRITICAL) Missing `getRowId` with selection + server refetches
|
|
377
|
+
|
|
378
|
+
The row-selection state is keyed by row ID. With index-as-ID, refetches that
|
|
379
|
+
return rows in any new order (sort flip, page change) reselect the wrong
|
|
380
|
+
rows. Always set `getRowId: row => row.id` (or whatever your primary key is).
|
|
381
|
+
|
|
382
|
+
### 4. (HIGH) "0 rows" flash between pages
|
|
383
|
+
|
|
384
|
+
If your fetch resolves to `undefined` during loading, `data.items` becomes `[]`
|
|
385
|
+
mid-fetch — the table renders empty for a frame. Use `linkedSignal` (or
|
|
386
|
+
`@tanstack/query`'s `placeholderData: keepPreviousData`, or
|
|
387
|
+
`httpResource`'s previous-value semantics) to keep the previous page visible.
|
|
388
|
+
|
|
389
|
+
### 5. (HIGH) Forgetting to handle both value AND updater-fn shapes in `on[State]Change`
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
// ❌ Crashes when the table passes an updater function
|
|
393
|
+
onPaginationChange: (value) => this.pagination.set(value)
|
|
394
|
+
|
|
395
|
+
// ✅
|
|
396
|
+
onPaginationChange: (u) =>
|
|
397
|
+
typeof u === 'function' ? this.pagination.update(u) : this.pagination.set(u)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### 6. (HIGH) `autoResetPageIndex` resetting your server pagination
|
|
401
|
+
|
|
402
|
+
By default, when data identity changes, the table resets to page 0. Under
|
|
403
|
+
server-driven pagination, _every_ fetch is a new array, so the table resets
|
|
404
|
+
the page index back to 0 every time. Set `autoResetPageIndex: false` and
|
|
405
|
+
manage page resets explicitly (e.g. reset on filter/sort change, but not on
|
|
406
|
+
the fetch itself).
|
|
407
|
+
|
|
408
|
+
### 7. (HIGH) Filtering on the client when only one page is loaded
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
manualPagination: true,
|
|
412
|
+
// columnFilteringFeature still registered, filteredRowModel still attached
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The filtered row model now filters only the _current page_ — useless. If the
|
|
416
|
+
server paginates, the server must also filter; flip `manualFiltering: true`
|
|
417
|
+
and drop the client `filteredRowModel`.
|
|
418
|
+
|
|
419
|
+
### 8. (HIGH) Forgetting to depend on the controlled signals in your fetch
|
|
420
|
+
|
|
421
|
+
If your `rxResource` / Query's `queryKey` doesn't read `pagination()`,
|
|
422
|
+
`sorting()`, `globalFilter()`, refetches won't happen. Both the table and the
|
|
423
|
+
fetcher must observe the same signals.
|
|
424
|
+
|
|
425
|
+
### 9. (MEDIUM) Reimplementing pagination state with raw `pageIndex` /
|
|
426
|
+
|
|
427
|
+
`pageSize` signals separate from the table
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
// ❌ Two sources of truth
|
|
431
|
+
readonly pageIndex = signal(0)
|
|
432
|
+
readonly pageSize = signal(10)
|
|
433
|
+
// table doesn't know about either
|
|
434
|
+
|
|
435
|
+
// ✅
|
|
436
|
+
readonly pagination = signal<PaginationState>({ pageIndex: 0, pageSize: 10 })
|
|
437
|
+
state: { pagination: this.pagination() }
|
|
438
|
+
onPaginationChange: ...
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Same lesson: use `setSorting`, not a manual sort signal that the table can't
|
|
442
|
+
see.
|
|
443
|
+
|
|
444
|
+
### 10. (MEDIUM) Not resetting `pageIndex` on filter/sort change
|
|
445
|
+
|
|
446
|
+
A common bug: user is on page 5, types in the filter, gets "no results" — but
|
|
447
|
+
the new filtered result set only has 2 pages. They have to manually click back
|
|
448
|
+
to page 1. Always reset `pageIndex` to 0 in `onGlobalFilterChange` /
|
|
449
|
+
`onColumnFiltersChange`.
|
|
450
|
+
|
|
451
|
+
### 11. (MEDIUM) Treating `_rowModels: {}` as "no row models work"
|
|
452
|
+
|
|
453
|
+
Core row model is always automatic. `table.getRowModel().rows` returns the
|
|
454
|
+
data array as `Row<...>` objects no matter what — `_rowModels: {}` just means
|
|
455
|
+
no client-side processing on top.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## See also
|
|
460
|
+
|
|
461
|
+
- `tanstack-table/angular/table-state` — state ownership, `state` vs `atoms`
|
|
462
|
+
- `tanstack-table/angular/compose-with-tanstack-query` — server fetch with Query
|
|
463
|
+
- `tanstack-table/angular/compose-with-tanstack-store` — external atom ownership
|
|
464
|
+
- `tanstack-table/core/filtering` — manualFiltering semantics
|
|
465
|
+
- `tanstack-table/core/sorting` — manualSorting semantics
|
|
466
|
+
- `tanstack-table/core/pagination` — manualPagination + `rowCount` / `pageCount`
|
|
467
|
+
- Example: `examples/angular/remote-data/`
|