@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,399 +0,0 @@
1
- ---
2
- name: angular/compose-with-tanstack-virtual
3
- description: >
4
- Compose TanStack Table v9 with `@tanstack/angular-virtual` for virtualized rendering of large
5
- row sets. TanStack Table does NOT virtualize on its own. Pattern: get `rows = table.getRowModel().rows`,
6
- feed `rows.length` to `injectVirtualizer({ count, estimateSize, getScrollElement, overscan })`,
7
- iterate `virtualizer.getVirtualItems()` in the template, position each row with
8
- `transform: translateY(item.start)` inside a tall sentinel, set
9
- `[style.height.px]="virtualizer.getTotalSize()"` to make the scrollbar correct. Handle the
10
- table-feature interactions: row-expanding (variable subRow heights → measure with
11
- `measureElement`), column sizing/pinning (column virtualization is separate),
12
- row-selection (selection state survives virtualization because it's keyed by row ID).
13
- type: composition
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/virtual:packages/angular-virtual/src/
24
- - TanStack/table:examples/angular/basic-inject-table/
25
- ---
26
-
27
- # Compose with TanStack Virtual (Angular)
28
-
29
- > TanStack Table is headless — it computes which rows / cells exist, but does
30
- > not decide which ones to render to the DOM. For tables larger than a few
31
- > hundred visible rows, pair with [`@tanstack/angular-virtual`](https://tanstack.com/virtual)
32
- > so only the rows in the viewport (+ overscan) actually mount.
33
- >
34
- > Required reading: `tanstack-table/angular/getting-started` and
35
- > `tanstack-table/angular/table-state`.
36
-
37
- ---
38
-
39
- ## 1. Install
40
-
41
- ```bash
42
- pnpm add @tanstack/angular-virtual
43
- ```
44
-
45
- Requires the same Angular version as `@tanstack/angular-table`.
46
-
47
- ---
48
-
49
- ## 2. The integration in one shape
50
-
51
- ```ts
52
- import {
53
- AfterViewInit,
54
- ChangeDetectionStrategy,
55
- Component,
56
- computed,
57
- signal,
58
- viewChild,
59
- ElementRef,
60
- } from '@angular/core'
61
- import {
62
- FlexRender,
63
- injectTable,
64
- tableFeatures,
65
- type ColumnDef,
66
- } from '@tanstack/angular-table'
67
- import { injectVirtualizer } from '@tanstack/angular-virtual'
68
-
69
- const features = tableFeatures({})
70
-
71
- @Component({
72
- selector: 'app-virtual-table',
73
- imports: [FlexRender],
74
- templateUrl: './virtual-table.html',
75
- styleUrl: './virtual-table.css',
76
- changeDetection: ChangeDetectionStrategy.OnPush,
77
- })
78
- export class VirtualTable {
79
- readonly data = signal<Array<Person>>(makeData(50_000))
80
- readonly scrollContainer =
81
- viewChild.required<ElementRef<HTMLDivElement>>('scroll')
82
-
83
- readonly table = injectTable(() => ({
84
- features,
85
- columns,
86
- data: this.data(),
87
- getRowId: (row) => row.id,
88
- }))
89
-
90
- // Stable reference to the rows array for the virtualizer
91
- readonly rows = computed(() => this.table.getRowModel().rows)
92
-
93
- readonly rowVirtualizer = injectVirtualizer(() => ({
94
- count: this.rows().length,
95
- getScrollElement: () => this.scrollContainer().nativeElement,
96
- estimateSize: () => 36, // fixed-height rows
97
- overscan: 10,
98
- }))
99
- }
100
- ```
101
-
102
- ```html
103
- <!-- virtual-table.html -->
104
- <div #scroll class="scroll-container" style="height: 600px; overflow: auto">
105
- <table style="display: grid">
106
- <thead style="display: grid; position: sticky; top: 0; z-index: 1">
107
- @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
108
- <tr style="display: flex; width: 100%">
109
- @for (header of headerGroup.headers; track header.id) {
110
- <th style="display: flex">
111
- <ng-container *flexRenderHeader="header; let value"
112
- >{{ value }}</ng-container
113
- >
114
- </th>
115
- }
116
- </tr>
117
- }
118
- </thead>
119
-
120
- <tbody
121
- style="display: grid; position: relative"
122
- [style.height.px]="rowVirtualizer.getTotalSize()"
123
- >
124
- @for (virtualRow of rowVirtualizer.getVirtualItems(); track
125
- virtualRow.key) { @let row = rows()[virtualRow.index];
126
- <tr
127
- [attr.data-index]="virtualRow.index"
128
- style="display: flex; position: absolute; width: 100%"
129
- [style.transform]="'translateY(' + virtualRow.start + 'px)'"
130
- >
131
- @for (cell of row.getVisibleCells(); track cell.id) {
132
- <td style="display: flex">
133
- <ng-container *flexRenderCell="cell; let value"
134
- >{{ value }}</ng-container
135
- >
136
- </td>
137
- }
138
- </tr>
139
- }
140
- </tbody>
141
- </table>
142
- </div>
143
- ```
144
-
145
- ### What's doing what
146
-
147
- - **Table** produces `rows` (`table.getRowModel().rows`). Length, identity,
148
- order are decided by registered features (sort, filter, pagination,
149
- grouping).
150
- - **Virtualizer** turns `rows.length` into the subset of "virtual items"
151
- currently visible (+ `overscan`). It tracks scroll on `getScrollElement()`
152
- and emits `getVirtualItems()` keyed by `virtualRow.key` (the row index by
153
- default).
154
- - **Template** renders only the virtual items, positions each with
155
- `translateY(virtualRow.start)` inside a sentinel of total height
156
- `getTotalSize()`. The scrollbar reflects the full row count, but only the
157
- visible window has DOM nodes.
158
-
159
- ---
160
-
161
- ## 3. Mandatory layout details
162
-
163
- This integration touches CSS in a few non-obvious places. None are optional:
164
-
165
- - **Scroll container has a fixed height** (`height: 600px` / `100vh` /
166
- whatever) **and `overflow: auto`**. The virtualizer needs both to compute
167
- visible range.
168
- - **Use `display: grid` on `<table>`, `display: flex` on `<thead>` /
169
- `<tbody>` / `<tr>`**, or use `<div>` markup. Native `<table>` layout
170
- defeats positioning rows absolutely. The virtual example above uses CSS
171
- grid to keep semantic table markup while letting the rows position freely.
172
- - **Container `<tbody>` is `position: relative` with explicit
173
- `height = virtualizer.getTotalSize()`.** Without that height, the scrollbar
174
- doesn't reflect the full data.
175
- - **Rows are `position: absolute; top: 0; left: 0; width: 100%` with
176
- `transform: translateY(virtualRow.start)`.**
177
- - **Sticky header**: `position: sticky; top: 0; z-index: 1` on the `<thead>`
178
- / its `<tr>` — the scroll container provides the scrolling.
179
-
180
- ---
181
-
182
- ## 4. Variable row heights — measure dynamically
183
-
184
- When rows can be different heights (expanded subRows, dynamic cell content),
185
- pass `measureElement` and a sensible `estimateSize`:
186
-
187
- ```ts
188
- readonly rowVirtualizer = injectVirtualizer(() => ({
189
- count: this.rows().length,
190
- getScrollElement: () => this.scrollContainer().nativeElement,
191
- estimateSize: () => 36,
192
- overscan: 10,
193
- measureElement: (element) => element?.getBoundingClientRect().height ?? 36,
194
- }))
195
- ```
196
-
197
- In the template, bind the element so the virtualizer can measure it:
198
-
199
- ```html
200
- <tr
201
- #rowEl
202
- [attr.data-index]="virtualRow.index"
203
- [virtualizerMeasureElement]="rowVirtualizer"
204
- ...
205
- ></tr>
206
- ```
207
-
208
- (See `@tanstack/angular-virtual` docs for the exact directive name and API;
209
- the principle is: every mounted row reports its real size, the virtualizer
210
- caches that, scrollbar adjusts.)
211
-
212
- ---
213
-
214
- ## 5. Row expanding — `rowExpandingFeature`
215
-
216
- Combine with `rowExpandingFeature` for "click to expand details":
217
-
218
- - Register `rowExpandingFeature` in `features` and
219
- `expandedRowModel: createExpandedRowModel()` as a slot on `features`.
220
- - Use `table.getExpandedRowModel().rows` (or `getRowModel().rows`, which
221
- already includes expansion under `paginateExpandedRows: true` semantics —
222
- see `tanstack-table/core/row-expanding`).
223
- - **Always use `measureElement`** because expansion changes row heights.
224
- - The virtualizer keys items by index; expanded subRows shift later rows
225
- down — that's correct and expected.
226
-
227
- ---
228
-
229
- ## 6. Row selection works transparently
230
-
231
- Row selection is keyed by row ID (`getRowId`), not by DOM presence. A row can
232
- be selected while off-screen; scrolling it into view shows the right checkbox
233
- state. **Always set `getRowId`** — critical for both selection and
234
- virtualizer key stability.
235
-
236
- ---
237
-
238
- ## 7. Column virtualization (horizontal)
239
-
240
- For very wide tables (50+ columns), virtualize columns too — a second
241
- `injectVirtualizer` over `table.getVisibleLeafColumns().length`. The pattern
242
- mirrors row virtualization but on the X axis. Combine with
243
- `columnPinningFeature` so pinned columns escape the virtualizer (always
244
- rendered, sticky).
245
-
246
- That's a meaningfully bigger lift — most tables don't need it. Reach for it
247
- only when you've profiled and column count is the bottleneck.
248
-
249
- ---
250
-
251
- ## 8. Interaction with pagination
252
-
253
- **If you paginate, you usually don't virtualize.** Pagination already caps
254
- the rendered row count to `pageSize`. Adding virtualization on top is
255
- typically wasted effort — you've already solved the rendering bottleneck.
256
-
257
- The exceptions:
258
-
259
- - Pages can hold thousands of rows (rare).
260
- - Pagination is "load more" / infinite scroll style — then virtualize the
261
- accumulated rows.
262
-
263
- ---
264
-
265
- ## 9. Interaction with sticky / pinned rows
266
-
267
- `rowPinningFeature` + virtualization is fiddly. Pinned rows live at the
268
- top/bottom of the table; they should render _outside_ the virtualizer's
269
- absolute positioning. Render them in dedicated `<thead>` /
270
- top/bottom-of-`<tbody>` sections, and call `table.getCenterRows()` (the
271
- non-pinned rows) to feed the virtualizer. See
272
- `tanstack-table/core/row-pinning` for the API surface.
273
-
274
- ---
275
-
276
- ## 10. SSR / first-paint
277
-
278
- On the server / first hydration, the scroll container's height is unknown;
279
- the virtualizer can render zero rows. Two mitigations:
280
-
281
- - Render a small initial chunk server-side (without the virtualizer) and let
282
- Angular hydrate into the virtualized version client-side.
283
- - Provide an explicit `initialRect: { width, height }` to the virtualizer
284
- options for SSR.
285
-
286
- ---
287
-
288
- ## Failure modes
289
-
290
- ### 1. (CRITICAL) Trying to use TanStack Table's own virtualization
291
-
292
- There is none. TanStack Table doesn't ship a virtualizer. If an agent
293
- suggests `getVirtualizedRows()` or `enableVirtualization: true` on the table —
294
- those don't exist. Use `@tanstack/angular-virtual`.
295
-
296
- ### 2. (CRITICAL) Missing height on the scroll container
297
-
298
- ```html
299
- <!-- ❌ no height → virtualizer reports 0 visible items → nothing renders -->
300
- <div #scroll style="overflow: auto">
301
- <!-- ✅ -->
302
- <div #scroll style="height: 600px; overflow: auto"></div>
303
- </div>
304
- ```
305
-
306
- The virtualizer measures the _scroll element_'s viewport. Without an explicit
307
- or computed height, the viewport is 0 and nothing renders.
308
-
309
- ### 3. (CRITICAL) Missing `getTotalSize()` height on the row container
310
-
311
- ```html
312
- <!-- ❌ scrollbar reflects only the rendered rows, not the full dataset -->
313
- <tbody>
314
- <!-- ✅ -->
315
- </tbody>
316
-
317
- <tbody [style.height.px]="rowVirtualizer.getTotalSize()"></tbody>
318
- ```
319
-
320
- Without this, you can scroll to the bottom of the _visible_ rows but can
321
- never reach row 1000. The scrollbar lies.
322
-
323
- ### 4. (CRITICAL) Forgetting `transform: translateY(...)` per row
324
-
325
- Absolutely-positioned rows without `transform` stack at `top: 0` — every row
326
- renders on top of every other.
327
-
328
- ### 5. (CRITICAL) Using native `<table>` layout with absolute-positioned rows
329
-
330
- Native `<table>` layout overrides positioning on `<tr>` / `<td>`. Either:
331
-
332
- - Use `display: grid` on `<table>` and `display: flex` on `<tr>` / `<td>`
333
- (see §3), preserving semantic markup, OR
334
- - Use `<div>` markup throughout.
335
-
336
- ### 6. (HIGH) Variable row heights without `measureElement`
337
-
338
- Default `estimateSize` is a constant. Different real heights → wrong
339
- positions → rows visually overlap or leave gaps. Pass `measureElement` and a
340
- way for each mounted row to report its real size.
341
-
342
- ### 7. (HIGH) Pagination + virtualization both enabled
343
-
344
- Pagination already caps row count. Adding virtualization on top doubles the
345
- indirection for no win. Pick one.
346
-
347
- ### 8. (HIGH) Reimplementing virtualization with `IntersectionObserver`
348
-
349
- Saw an agent build a homegrown "render rows when visible" with
350
- `IntersectionObserver`? That's hundreds of lines of broken virtualization.
351
- Use the library.
352
-
353
- ### 9. (HIGH) Wrong `track` in the virtualized `@for`
354
-
355
- ```html
356
- <!-- ❌ tracking by row.id confuses Angular because positions shift -->
357
- @for (virtualRow of rowVirtualizer.getVirtualItems(); track row.id)
358
-
359
- <!-- ✅ -->
360
- @for (virtualRow of rowVirtualizer.getVirtualItems(); track virtualRow.key)
361
- ```
362
-
363
- Track by the virtual item's stable key (or index). The row is _inside_ the
364
- virtual item — Angular uses the outer track for DOM reuse.
365
-
366
- ### 10. (MEDIUM) `injectVirtualizer` outside an injection context
367
-
368
- Like `injectTable`, `injectVirtualizer` calls `assertInInjectionContext()`.
369
- Place it on a class field, in a constructor, or inside `runInInjectionContext`.
370
-
371
- ### 11. (MEDIUM) Recreating `count` / `estimateSize` on every signal change without
372
-
373
- stable callbacks
374
-
375
- Move `estimateSize`, `measureElement`, `getScrollElement` to stable
376
- references (class arrow methods or module-scope functions) where possible.
377
- Otherwise the virtualizer re-initializes its internal state on every change.
378
-
379
- ### 12. (MEDIUM) Missing `getRowId` — selection breaks across re-sorts in a
380
-
381
- virtualized table
382
-
383
- This isn't virtualization-specific, but it's especially visible here because
384
- virtualization renders a window of rows; refreshing that window via scroll
385
- makes mismatched checkbox state obvious. `getRowId: (row) => row.id` is
386
- mandatory.
387
-
388
- ---
389
-
390
- ## See also
391
-
392
- - `tanstack-table/angular/getting-started` — baseline table that this skill
393
- layers virtualization on top of
394
- - `tanstack-table/angular/production-readiness` — when to reach for
395
- virtualization vs server-side pagination
396
- - `tanstack-table/core/row-expanding` — variable subRow heights + virtual
397
- - `tanstack-table/core/column-layout` — pinning interaction
398
- - `@tanstack/angular-virtual` docs — `injectVirtualizer`, options reference,
399
- variable-height patterns