@tanstack/angular-table 9.0.0-beta.38 → 9.0.0-beta.42

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