@tanstack/angular-table 9.0.0-alpha.46 → 9.0.0-alpha.48

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.
@@ -0,0 +1,468 @@
1
+ ---
2
+ name: angular/production-readiness
3
+ description: >
4
+ Ship-ready optimizations for Angular Table v9: register only the `_features` you actually use
5
+ (tree-shake the bundle); keep `columns` / `_features` / `_rowModels` / feature-fn maps as
6
+ stable references OUTSIDE the `injectTable` initializer; pass only the `*Fns` your data needs
7
+ to `createSortedRowModel` / `createFilteredRowModel` / `createGroupedRowModel`; use
8
+ `ChangeDetectionStrategy.OnPush`; lean on signal-backed atoms (`table.atoms.<slice>.get()`)
9
+ instead of broad `table.store.state` reads where granularity matters; use `{ equal: shallow }`
10
+ on object/array `computed` selectors; set `getRowId` for stable identity; track by `id` in
11
+ every `@for`; defer cell components with `flexRenderComponent` only when you need its options;
12
+ scope DI tokens via `[tanStackTable*]` directives to kill prop drilling.
13
+ type: lifecycle
14
+ library: tanstack-table
15
+ framework: angular
16
+ library_version: '9.0.0-alpha.47'
17
+ requires:
18
+ - angular/table-state
19
+ - angular/getting-started
20
+ - angular/angular-rendering-directives
21
+ sources:
22
+ - TanStack/table:docs/framework/angular/angular-table.md
23
+ - TanStack/table:docs/framework/angular/guide/table-state.md
24
+ - TanStack/table:docs/framework/angular/guide/migrating.md
25
+ - TanStack/table:packages/angular-table/src/injectTable.ts
26
+ - TanStack/table:packages/angular-table/src/reactivity.ts
27
+ - TanStack/table:examples/angular/composable-tables/
28
+ ---
29
+
30
+ # Production Readiness (Angular Table v9)
31
+
32
+ > Once your table compiles and renders, this is the cost reduction pass.
33
+ > Angular's signal-backed adapter makes most of v9's perf "free" if you don't
34
+ > fight it — the work is mostly about _what you don't do_: not recreating
35
+ > objects, not pulling in features you don't use, not over-wrapping with
36
+ > selectors.
37
+
38
+ ---
39
+
40
+ ## 1. Bundle: register only the features you use
41
+
42
+ The single biggest v9 win is feature tree-shaking. Every feature you put in
43
+ `tableFeatures({...})` pulls in its code; everything you leave out is dropped
44
+ by the bundler.
45
+
46
+ ```ts
47
+ // ❌ Pulls in EVERY feature, even unused ones
48
+ const _features = stockFeatures
49
+
50
+ // ✅ Only what this table actually uses
51
+ const _features = tableFeatures({
52
+ rowSortingFeature,
53
+ rowPaginationFeature,
54
+ columnFilteringFeature,
55
+ })
56
+ ```
57
+
58
+ Use `stockFeatures` to bootstrap during a v8 → v9 migration, then **come back
59
+ and curate**. The bundle wins only land once you do.
60
+
61
+ The same applies to feature-fn registries — pass only the `*Fns` your data
62
+ needs:
63
+
64
+ ```ts
65
+ import {
66
+ createSortedRowModel,
67
+ createFilteredRowModel,
68
+ sortFns,
69
+ filterFns,
70
+ } from '@tanstack/angular-table'
71
+
72
+ // ❌ pulls in every built-in sort + filter fn
73
+ _rowModels: {
74
+ sortedRowModel: createSortedRowModel(sortFns),
75
+ filteredRowModel: createFilteredRowModel(filterFns),
76
+ }
77
+
78
+ // ✅ only what you use
79
+ _rowModels: {
80
+ sortedRowModel: createSortedRowModel({ basic: sortFns.basic, datetime: sortFns.datetime }),
81
+ filteredRowModel: createFilteredRowModel({ includesString: filterFns.includesString }),
82
+ }
83
+ ```
84
+
85
+ Same logic for `aggregationFns` if you use grouping.
86
+
87
+ ---
88
+
89
+ ## 2. Stable references — keep them OUTSIDE the initializer
90
+
91
+ `injectTable(() => ({...}))` **re-runs the initializer every time a signal read
92
+ inside it changes** and then calls `table.setOptions({ ...prev, ...new })`.
93
+ Anything you create inside the initializer is recreated on every signal
94
+ change.
95
+
96
+ ```ts
97
+ // ❌ columns / _features / _rowModels / feature fns recreated on every data() change
98
+ @Component({...})
99
+ export class App {
100
+ readonly table = injectTable(() => ({
101
+ _features: tableFeatures({ rowSortingFeature }), // ← new ref each run
102
+ _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, // ← new ref each run
103
+ columns: [/* … */], // ← new ref each run
104
+ data: this.data(),
105
+ }))
106
+ }
107
+
108
+ // ✅ stable references outside; only reactive reads inside
109
+ const _features = tableFeatures({ rowSortingFeature })
110
+ const _rowModels = { sortedRowModel: createSortedRowModel(sortFns) }
111
+ const columns: Array<ColumnDef<typeof _features, Person>> = [/* … */]
112
+
113
+ @Component({...})
114
+ export class App {
115
+ readonly table = injectTable(() => ({
116
+ _features,
117
+ _rowModels,
118
+ columns,
119
+ data: this.data(),
120
+ }))
121
+ }
122
+ ```
123
+
124
+ Same rule for the controlled-state pattern — keep `state: { pagination: this.pagination() }`
125
+ inside the initializer, but keep the signal definitions on the class.
126
+
127
+ For shared infrastructure across multiple tables, `createTableHook(...)` lets
128
+ you define `_features` / `_rowModels` / default options once at module scope.
129
+
130
+ ---
131
+
132
+ ## 3. `ChangeDetectionStrategy.OnPush` everywhere
133
+
134
+ Every component that hosts or renders a TanStack Table should be:
135
+
136
+ ```ts
137
+ @Component({
138
+ // ...
139
+ changeDetection: ChangeDetectionStrategy.OnPush,
140
+ })
141
+ ```
142
+
143
+ With signal-backed atoms, OnPush is sufficient — atom reads in the template
144
+ are tracked through `computed`, so Angular schedules a check when the signal
145
+ changes. Default change detection causes redundant work on every event.
146
+
147
+ All `examples/angular/*` use `OnPush`. Match that.
148
+
149
+ ---
150
+
151
+ ## 4. Read narrowly — `table.atoms.<slice>.get()` over `table.store.state`
152
+
153
+ Both surfaces are signal-backed. The difference is _which signal_ gets read.
154
+
155
+ ```ts
156
+ // Wider — depends on the flat snapshot signal (recomputes when ANY registered slice changes)
157
+ const pageIndex = computed(() => this.table.store.state.pagination.pageIndex)
158
+
159
+ // Narrower — depends only on the pagination atom
160
+ const pageIndex = computed(() => this.table.atoms.pagination.get().pageIndex)
161
+ ```
162
+
163
+ For most apps the difference is negligible. For high-frequency atoms or
164
+ deeply-derived components, prefer per-atom reads.
165
+
166
+ ---
167
+
168
+ ## 5. `{ equal: shallow }` on object/array `computed`
169
+
170
+ When you derive an object or array slice, downstream `effect`s and `computed`s
171
+ re-run whenever the reference changes — even if the structural contents
172
+ didn't. Use `shallow` from `@tanstack/angular-table` to short-circuit:
173
+
174
+ ```ts
175
+ import { computed } from '@angular/core'
176
+ import { shallow } from '@tanstack/angular-table'
177
+
178
+ readonly pagination = computed(
179
+ () => this.table.atoms.pagination.get(),
180
+ { equal: shallow },
181
+ )
182
+
183
+ readonly visibleColumns = computed(
184
+ () => this.table.atoms.columnVisibility.get(),
185
+ { equal: shallow },
186
+ )
187
+ ```
188
+
189
+ This is **not** about reactivity — atoms are reactive already. This is about
190
+ skipping no-op downstream recomputations when the slice rebuilds with the same
191
+ values.
192
+
193
+ **Don't reach for it on every read.** Reserve it for derived selectors whose
194
+ downstream is expensive (effects that hit the server, big template
195
+ re-renders).
196
+
197
+ ---
198
+
199
+ ## 6. `track row.id` and `getRowId`
200
+
201
+ Always provide a stable identity:
202
+
203
+ ```ts
204
+ readonly table = injectTable(() => ({
205
+ // ...
206
+ getRowId: (row) => row.id, // stable primary key
207
+ }))
208
+ ```
209
+
210
+ Then in every `@for`:
211
+
212
+ ```html
213
+ @for (row of table.getRowModel().rows; track row.id) { ... } @for (cell of
214
+ row.getVisibleCells(); track cell.id) { ... } @for (header of
215
+ headerGroup.headers; track header.id) { ... }
216
+ ```
217
+
218
+ Without `getRowId`, IDs default to row index — re-rendering the entire row list
219
+ on a sort flip, refetch, or pagination move because Angular thinks every row
220
+ is new.
221
+
222
+ ---
223
+
224
+ ## 7. Render-cost rules for cells
225
+
226
+ Cell render fns run for every visible cell on every re-render. Cheap is the
227
+ goal.
228
+
229
+ - **Return a primitive when you can.** `cell: (info) => info.getValue()` is
230
+ fastest.
231
+ - **Return a component class (not a wrapper) when only inputs need wiring.**
232
+ The renderer's `KeyValueDiffers` skips `setInput` for unchanged values.
233
+ - **Reach for `flexRenderComponent(...)` only for explicit options** —
234
+ custom inputs not derived from context, output callbacks, an injector,
235
+ Angular v20+ `bindings` / `directives`.
236
+ - **Don't put expensive `inject()` calls in render fns.** They run inside
237
+ `runInInjectionContext` every render. Inject at the component level and
238
+ close over the value.
239
+ - **Don't allocate inside render fns when you can avoid it.** Closures, new
240
+ array literals, etc.
241
+
242
+ ### Stable input references
243
+
244
+ For object inputs (like `data` arrays you pass into a sub-component), keep the
245
+ reference stable across renders. `KeyValueDiffers` is reference-based for
246
+ Angular's default input equality, so a `{ ...obj }` literal on every render
247
+ defeats it.
248
+
249
+ ---
250
+
251
+ ## 8. Kill prop drilling with DI tokens
252
+
253
+ Passing `cell` / `header` / `table` through 2+ component layers is both
254
+ ergonomic noise and a perf hazard (each input has its own diffing cost).
255
+ Replace with the host directives + inject helpers:
256
+
257
+ ```html
258
+ <td [tanStackTableCell]="cell">
259
+ <ng-container *flexRenderCell="cell; let value">{{ value }}</ng-container>
260
+ <app-cell-actions />
261
+ <!-- no `cell` input needed -->
262
+ </td>
263
+ ```
264
+
265
+ ```ts
266
+ export class CellActionsComponent {
267
+ readonly cell = injectTableCellContext()
268
+ // cell() is a Signal<Cell<...>>; reads are reactive
269
+ }
270
+ ```
271
+
272
+ Inside `*flexRender*` components, the tokens are auto-provided (no host
273
+ directive needed) — see `tanstack-table/angular/angular-rendering-directives`
274
+ §7.
275
+
276
+ ---
277
+
278
+ ## 9. Large data — let virtualization do the work
279
+
280
+ A 10k-row table is fine in v9 in terms of state, but rendering 10k rows is
281
+ slow. **Don't render what's off-screen.** Pair with `@tanstack/angular-virtual`
282
+ — see `tanstack-table/angular/compose-with-tanstack-virtual`.
283
+
284
+ Also consider:
285
+
286
+ - Server-side pagination if data is huge — see
287
+ `tanstack-table/angular/client-to-server`.
288
+ - `defaultColumn: { size, minSize, maxSize }` to set sane sizing defaults if
289
+ you've registered `columnSizingFeature`.
290
+
291
+ ---
292
+
293
+ ## 10. Avoid `effect(...)` for cross-slice sync — write directly
294
+
295
+ ```ts
296
+ // ❌ Effect chain — runs after CD, can layer-cake
297
+ effect(() => {
298
+ const filter = this.globalFilter()
299
+ this.pagination.update((p) => ({ ...p, pageIndex: 0 }))
300
+ })
301
+
302
+ // ✅ Reset inline in the on*Change handler
303
+ onGlobalFilterChange: (u) => {
304
+ typeof u === 'function'
305
+ ? this.globalFilter.update(u)
306
+ : this.globalFilter.set(u)
307
+ this.pagination.update((p) => ({ ...p, pageIndex: 0 }))
308
+ }
309
+ ```
310
+
311
+ The on\*Change handler runs synchronously at the source of truth; an `effect`
312
+ runs after Angular's CD pass, which can lead to double renders.
313
+
314
+ ---
315
+
316
+ ## 11. Avoid double-controlling a slice
317
+
318
+ Don't supply both `state.x` and `atoms.x` for the same slice — atom wins
319
+ silently, the Angular signal becomes a write-only sink, and you've doubled
320
+ the wiring cost. Pick exactly one source of truth per slice (see
321
+ `tanstack-table/angular/table-state` §6–§7).
322
+
323
+ ---
324
+
325
+ ## 12. Build hygiene
326
+
327
+ - **`bundle-stats` / `source-map-explorer`**: after curating `_features`,
328
+ verify your final bundle doesn't include retired features. If you see
329
+ `rowGroupingFeature` in the bundle but never imported it, something is
330
+ pulling in `stockFeatures` indirectly.
331
+ - **`debugTable: isDevMode()`** — only in dev. Don't leave `debugTable: true`
332
+ in production.
333
+
334
+ ---
335
+
336
+ ## 13. Quick wins checklist
337
+
338
+ - [ ] `_features` listed explicitly (no `stockFeatures` in production).
339
+ - [ ] `*Fns` registries passed only what you use to
340
+ `createSortedRowModel` / `createFilteredRowModel` /
341
+ `createGroupedRowModel`.
342
+ - [ ] `columns`, `_features`, `_rowModels`, feature fns are at module scope
343
+ or stable class fields — never inside the `injectTable` initializer.
344
+ - [ ] Component is `ChangeDetectionStrategy.OnPush`.
345
+ - [ ] `getRowId` set when rows have a stable primary key.
346
+ - [ ] All `@for` blocks track by `id`.
347
+ - [ ] Cell render fns return primitives or component classes when possible;
348
+ `flexRenderComponent(...)` reserved for explicit-option cases.
349
+ - [ ] Reading state inside `effect`s / heavy `computed`s uses
350
+ `table.atoms.<slice>.get()` (not the flat snapshot).
351
+ - [ ] Object/array `computed` selectors that feed expensive downstream use
352
+ `{ equal: shallow }`.
353
+ - [ ] No `cell` / `header` / `table` inputs drilled through multiple
354
+ components — replaced with `injectTableCellContext()` / etc.
355
+ - [ ] `debugTable` only in `isDevMode()`.
356
+ - [ ] Tables larger than a few hundred visible rows use virtualization.
357
+
358
+ ---
359
+
360
+ ## Failure modes
361
+
362
+ ### 1. (CRITICAL) Shipping `stockFeatures` to production
363
+
364
+ `stockFeatures` defeats the v9 bundle wins. The migrating skill explicitly
365
+ calls this out — `stockFeatures` is a v8 → v9 bootstrap, not a production
366
+ end-state.
367
+
368
+ ### 2. (CRITICAL) Recreating `columns` / `_features` / `_rowModels` inside the
369
+
370
+ `injectTable` initializer
371
+
372
+ The initializer re-runs on every signal read change. New `columns` reference
373
+ triggers full column-model rebuilds — for big tables this is visibly slow.
374
+ Module-scope it.
375
+
376
+ ### 3. (CRITICAL) Reimplementing what the table already does
377
+
378
+ Symptoms:
379
+
380
+ - Manual sort on the data array inside a `computed`/`effect`, then passing
381
+ the sorted array to the table.
382
+ - Manual pagination math driving `data: paged()` — paginated by the user, not
383
+ the table.
384
+ - Hand-rolled global filter `.filter(...)` inside `effect`.
385
+
386
+ All of these are far slower than the built-in row models (which memoize and
387
+ short-circuit) and ship more code. Use `table.setSorting(...)`,
388
+ `table.setColumnFilters(...)`, the registered `_rowModels` factories.
389
+
390
+ ### 4. (HIGH) `OnPush` not set
391
+
392
+ Default change detection runs on every event in the entire app. Even with
393
+ signal-backed atoms, you're paying for unnecessary template checks. `OnPush`
394
+ is the table's idiomatic setting.
395
+
396
+ ### 5. (HIGH) `@for` without stable `track`
397
+
398
+ `@for (row of rows)` without a `track` value at all is a build error in
399
+ Angular ≥17 strict mode; `track $index` defeats DOM reuse on sort/refetch.
400
+ Always `track row.id` (and `getRowId` on the table).
401
+
402
+ ### 6. (HIGH) Over-wrapping every read in `computed(...)`
403
+
404
+ ```ts
405
+ // ❌ adds a computed layer for no reason
406
+ readonly pagination = computed(() => this.table.atoms.pagination.get())
407
+ ```
408
+
409
+ The atom is already signal-backed. Use `computed` for derivation, custom
410
+ equality, or shared selectors — not for "make it reactive."
411
+
412
+ ### 7. (HIGH) `{ equal: shallow }` on every `computed`
413
+
414
+ Shallow equality has a runtime cost (one pass over keys). For primitive
415
+ selectors it's strictly slower than `Object.is`. Reserve it for derived
416
+ object/array slices whose downstream is expensive.
417
+
418
+ ### 8. (HIGH) Drilling `cell` / `header` / `table` through multiple
419
+
420
+ components
421
+
422
+ Inputs add diffing cost on every change-detection cycle. Replace with the
423
+ `[tanStackTableCell]` / `[tanStackTableHeader]` / `[tanStackTable]` host
424
+ directives and `injectTableCellContext()` / etc. at the leaf.
425
+
426
+ ### 9. (HIGH) `flexRenderComponent(...)` for every cell
427
+
428
+ `flexRenderComponent` adds a wrapper with `reflectComponentType` overhead
429
+ and an `OutputEmitterRef` subscription scan. For plain component pass-through
430
+ where context inputs cover everything, **return the component class
431
+ directly** — the renderer does `setInput` on its own.
432
+
433
+ ### 10. (MEDIUM) `effect(...)` chains for what should be on\*Change inline
434
+
435
+ If the user changes `globalFilter` and your `pagination` reset lives in an
436
+ `effect`, you get a CD pass for the filter and a second one for the reset.
437
+ Inline the reset in `onGlobalFilterChange`.
438
+
439
+ ### 11. (MEDIUM) Forgetting `autoResetPageIndex: false` for server-driven tables
440
+
441
+ Every fetch produces a new array reference, which triggers the default
442
+ auto-reset and bounces the user back to page 0 mid-pagination. See
443
+ `tanstack-table/angular/client-to-server` §9.
444
+
445
+ ### 12. (MEDIUM) `debugTable: true` left in production
446
+
447
+ Turns on per-operation `console.info` logging from the core. Use
448
+ `debugTable: isDevMode()`.
449
+
450
+ ### 13. (MEDIUM) Reaching for `Subscribe` patterns ported from React docs
451
+
452
+ Angular doesn't need a `Subscribe` boundary the way React does. The
453
+ adapter's signal binding handles fine-grained reactivity at the atom level —
454
+ templates re-evaluate the dependencies they actually read.
455
+
456
+ ---
457
+
458
+ ## See also
459
+
460
+ - `tanstack-table/angular/getting-started` — the first-table baseline
461
+ - `tanstack-table/angular/table-state` — narrow vs wide reads, controlled state
462
+ - `tanstack-table/angular/angular-rendering-directives` — `flexRenderComponent`,
463
+ DI tokens
464
+ - `tanstack-table/angular/client-to-server` — server-driven optimizations
465
+ - `tanstack-table/angular/compose-with-tanstack-virtual` — virtualizing big
466
+ tables
467
+ - Example: `examples/angular/composable-tables/` — `createTableHook` for
468
+ app-wide infrastructure