@tanstack/angular-table 9.0.0-alpha.5 → 9.0.0-alpha.51

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