@tanstack/angular-table 9.0.0-beta.2 → 9.0.0-beta.21

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.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Type, ComponentMirror, OutputEmitterRef, InputSignal, Injector, createComponent, Binding, TemplateRef, InjectionToken, Signal } from '@angular/core';
3
- import { TableFeatures, RowData, CellData, Cell, Header, CellContext, HeaderContext, Table, TableState, TableOptions, Column, Row, IdentifiedColumnDef, AccessorFn, DeepKeys, DeepValue, AccessorFnColumnDef, AccessorKeyColumnDef, ColumnDef, DisplayColumnDef, GroupColumnDef } from '@tanstack/table-core';
3
+ import { TableFeatures, RowData, CellData, Cell, Header, CellContext, HeaderContext, Table, TableOptions, Column, Row, IdentifiedColumnDef, AccessorFn, DeepKeys, DeepValue, AccessorFnColumnDef, AccessorKeyColumnDef, ColumnDef, DisplayColumnDef, GroupColumnDef } from '@tanstack/table-core';
4
4
  export * from '@tanstack/table-core';
5
5
  import { Atom, ReadonlyAtom, Store, ReadonlyStore } from '@tanstack/angular-store';
6
6
  export { shallow } from '@tanstack/angular-store';
@@ -365,16 +365,11 @@ type SubscribeSource<TValue> = Atom<TValue> | ReadonlyAtom<TValue> | Store<TValu
365
365
  type AngularTable<TFeatures extends TableFeatures, TData extends RowData> = Table<TFeatures, TData> & {
366
366
  /**
367
367
  * @deprecated Prefer `table.atoms.<slice>.get()` for template/render reads
368
- * of a specific state slice, `table.state` for full-state debug snapshots, or
369
- * Angular computed values around explicit selectors. `table.store.state` is a
370
- * current-value snapshot and is easy to misuse in render code.
368
+ * of a specific state slice, or Angular computed values around explicit
369
+ * selectors. `table.store.state` is a current-value snapshot and is easy to
370
+ * misuse in render code.
371
371
  */
372
372
  readonly store: Table<TFeatures, TData>['store'];
373
- /**
374
- * The current table state exposed as a flat proxy. Prefer
375
- * `table.atoms.<slice>.get()` when reading a specific slice.
376
- */
377
- readonly state: Readonly<TableState<TFeatures>>;
378
373
  };
379
374
  /**
380
375
  * Creates and returns an Angular-reactive table instance.
@@ -449,7 +444,7 @@ interface TanStackTableCellContext<TFeatures extends TableFeatures, TData extend
449
444
  *
450
445
  * This token is provided by the {@link TanStackTableCell} directive.
451
446
  */
452
- declare const TanStackTableCellToken: InjectionToken<Signal<any>>;
447
+ declare const TanStackTableCellToken: InjectionToken<Signal<Cell<TableFeatures, RowData, unknown>>>;
453
448
  /**
454
449
  * Provides a TanStack Table `Cell` instance in Angular DI.
455
450
  *
@@ -521,7 +516,7 @@ interface TanStackTableHeaderContext<TFeatures extends TableFeatures, TData exte
521
516
  *
522
517
  * This token is provided by the {@link TanStackTableHeader} directive.
523
518
  */
524
- declare const TanStackTableHeaderToken: InjectionToken<Signal<any>>;
519
+ declare const TanStackTableHeaderToken: InjectionToken<Signal<Header<TableFeatures, RowData, unknown>>>;
525
520
  /**
526
521
  * Provides a TanStack Table `Header` instance in Angular DI.
527
522
  *
@@ -578,7 +573,7 @@ declare function injectTableHeaderContext<TFeatures extends TableFeatures, TData
578
573
  *
579
574
  * This token is provided by the {@link TanStackTable} directive.
580
575
  */
581
- declare const TanStackTableToken: InjectionToken<Signal<any>>;
576
+ declare const TanStackTableToken: InjectionToken<Signal<AngularTable<TableFeatures, RowData>>>;
582
577
  /**
583
578
  * Provides a TanStack Table instance (`AngularTable`) in Angular DI.
584
579
  *
@@ -756,7 +751,7 @@ type CreateTableHookResult<TFeatures extends TableFeatures, TTableComponents ext
756
751
  injectTableCellContext: <TValue extends CellData = CellData, TRowData extends RowData = RowData>() => Signal<Cell<TFeatures, TRowData, TValue>>;
757
752
  injectFlexRenderHeaderContext: <TData extends RowData, TValue extends CellData>() => HeaderContext<TFeatures, TData, TValue>;
758
753
  injectFlexRenderCellContext: <TData extends RowData, TValue extends CellData>() => CellContext<TFeatures, TData, TValue>;
759
- injectAppTable: <TData extends RowData>(tableOptions: () => Omit<TableOptions<TFeatures, TData>, 'features' | 'rowModels'>) => AppAngularTable<TFeatures, TData, TTableComponents, TCellComponents, THeaderComponents>;
754
+ injectAppTable: <TData extends RowData>(tableOptions: () => Omit<TableOptions<TFeatures, TData>, 'features'>) => AppAngularTable<TFeatures, TData, TTableComponents, TCellComponents, THeaderComponents>;
760
755
  };
761
756
  /**
762
757
  * Creates app-scoped Angular table helpers with features, row models, and
@@ -770,7 +765,6 @@ type CreateTableHookResult<TFeatures extends TableFeatures, TTableComponents ext
770
765
  * ```ts
771
766
  * const { injectAppTable, createAppColumnHelper } = createTableHook({
772
767
  * features,
773
- * rowModels: {},
774
768
  * tableComponents: {},
775
769
  * cellComponents: {},
776
770
  * headerComponents: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/angular-table",
3
- "version": "9.0.0-beta.2",
3
+ "version": "9.0.0-beta.21",
4
4
  "description": "Headless UI for building powerful tables & datagrids for Angular.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -52,14 +52,15 @@
52
52
  ],
53
53
  "dependencies": {
54
54
  "@tanstack/angular-store": "^0.11.0",
55
- "@tanstack/table-core": "9.0.0-beta.1",
56
- "tslib": "^2.8.1"
55
+ "tslib": "^2.8.1",
56
+ "@tanstack/table-core": "9.0.0-beta.21"
57
57
  },
58
58
  "devDependencies": {
59
- "@analogjs/vite-plugin-angular": "^2.6.0",
60
- "@analogjs/vitest-angular": "^2.6.0",
61
- "@angular/core": "^22.0.0",
62
- "@angular/platform-browser": "^22.0.0",
59
+ "@analogjs/vite-plugin-angular": "^2.6.1",
60
+ "@analogjs/vitest-angular": "^2.6.1",
61
+ "@angular/compiler": "^22.0.2",
62
+ "@angular/core": "^22.0.2",
63
+ "@angular/platform-browser": "^22.0.2",
63
64
  "ng-packagr": "^22.0.0",
64
65
  "typescript": "6.0.3"
65
66
  },
@@ -297,10 +297,10 @@ column defs short and typed.
297
297
 
298
298
  ```ts
299
299
  export const { injectAppTable, createAppColumnHelper } = createTableHook({
300
- features: tableFeatures({ rowSortingFeature }),
301
- rowModels: {
302
- /* */
303
- },
300
+ features: tableFeatures({
301
+ rowSortingFeature,
302
+ /* row-model factories and fn registries also go here */
303
+ }),
304
304
  tableComponents: { PaginationControls, RowCount },
305
305
  cellComponents: { TextCell, NumberCell, StatusCell },
306
306
  headerComponents: { SortIndicator, ColumnFilter },
@@ -26,10 +26,10 @@ export const {
26
26
  injectTableCellContext,
27
27
  injectTableHeaderContext,
28
28
  } = createTableHook({
29
- features: tableFeatures({ rowSortingFeature }),
30
- rowModels: {
31
- /* */
32
- },
29
+ features: tableFeatures({
30
+ rowSortingFeature,
31
+ /* row-model factories and fn registries also go here */
32
+ }),
33
33
  tableComponents: { PaginationControls, RowCount },
34
34
  cellComponents: { TextCell, NumberCell, StatusCell },
35
35
  headerComponents: { SortIndicator, ColumnFilter },
@@ -3,12 +3,13 @@ name: angular/client-to-server
3
3
  description: >
4
4
  Convert an Angular Table v9 from client-side to server-side processing. Flip
5
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.
6
+ for the slices the server now owns; drop the corresponding row-model factory slots from the
7
+ `features` object for the factories the server replaces; supply `rowCount` (server total) so
8
+ pagination computes correctly; hoist `pagination` / `sorting` / `columnFilters` / `globalFilter`
9
+ to Angular signals with `state` + `on[State]Change`; fetch via `rxResource` / `httpResource` /
10
+ `@tanstack/angular-query`; preserve previous data on refetch with `linkedSignal` (or
11
+ `placeholderData: keepPreviousData` for Query); set `getRowId` for stable selection across
12
+ refetches.
12
13
  type: lifecycle
13
14
  library: tanstack-table
14
15
  framework: angular
@@ -44,7 +45,7 @@ For each slice the server now owns:
44
45
 
45
46
  1. **Flip `manualX: true`** in table options. This tells the table "don't
46
47
  process this on the client — trust the data you receive."
47
- 2. **Drop the matching client-side row-model factory** from `rowModels`
48
+ 2. **Drop the matching client-side row-model factory slot** from `features`
48
49
  (or keep it if you still want the feature's _state_ but no client
49
50
  recomputation — see §3).
50
51
  3. **Hoist the slice to an Angular signal**, control it via `state.x` +
@@ -97,6 +98,7 @@ import {
97
98
  tableFeatures,
98
99
  rowPaginationFeature,
99
100
  rowSortingFeature,
101
+ columnFilteringFeature,
100
102
  globalFilteringFeature,
101
103
  createColumnHelper,
102
104
  type ColumnDef,
@@ -107,6 +109,7 @@ import {
107
109
  const features = tableFeatures({
108
110
  rowPaginationFeature,
109
111
  rowSortingFeature,
112
+ columnFilteringFeature,
110
113
  globalFilteringFeature,
111
114
  })
112
115
 
@@ -198,12 +201,11 @@ export class App {
198
201
  }),
199
202
  ]
200
203
 
201
- // 4. Wire the table — manualX flags, drop row-model factories, supply rowCount
204
+ // 4. Wire the table — manualX flags, features without row-model factories, supply rowCount
202
205
  readonly table = injectTable(() => {
203
206
  const data = this.dataWithLatest()
204
207
  return {
205
- features,
206
- rowModels: {}, // ← dropped paginatedRowModel, sortedRowModel, filteredRowModel
208
+ features, // ← features has no paginatedRowModel/sortedRowModel/filteredRowModel slots
207
209
  columns: this.columns,
208
210
  data: data.items,
209
211
  getRowId: (row) => String(row.id),
@@ -245,8 +247,8 @@ export class App {
245
247
 
246
248
  ### What changed from the client-side version
247
249
 
248
- - `rowModels: {}` no `paginatedRowModel`, no `sortedRowModel`, no
249
- `filteredRowModel`. The server is the source of truth.
250
+ - `features` has no `paginatedRowModel`, `sortedRowModel`, or `filteredRowModel`
251
+ slots the server is the source of truth for those.
250
252
  - `manualPagination` / `manualSorting` / `manualFiltering: true`.
251
253
  - `rowCount: data.totalCount` — required for correct `getPageCount()` and the
252
254
  next/prev buttons.
@@ -359,13 +361,12 @@ edits (toggle the flag around the update).
359
361
 
360
362
  ## Failure modes
361
363
 
362
- ### 1. (CRITICAL) Flipping `manualPagination: true` but keeping
363
-
364
- `paginatedRowModel` in `rowModels`
364
+ ### 1. (CRITICAL) Flipping `manualPagination: true` but keeping `paginatedRowModel` on `features`
365
365
 
366
366
  The client row-model factory will re-paginate the (already-paginated) data,
367
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.
368
+ **Remove the `paginatedRowModel` slot from `features`** when going manual — or
369
+ accept double-pagination.
369
370
 
370
371
  ### 2. (CRITICAL) Forgetting `rowCount` under `manualPagination`
371
372
 
@@ -409,12 +410,12 @@ the fetch itself).
409
410
 
410
411
  ```ts
411
412
  manualPagination: true,
412
- // columnFilteringFeature still registered, filteredRowModel still attached
413
+ // columnFilteringFeature still registered, filteredRowModel slot still on features
413
414
  ```
414
415
 
415
416
  The filtered row model now filters only the _current page_ — useless. If the
416
417
  server paginates, the server must also filter; flip `manualFiltering: true`
417
- and drop the client `filteredRowModel`.
418
+ and remove the `filteredRowModel` slot from `features`.
418
419
 
419
420
  ### 8. (HIGH) Forgetting to depend on the controlled signals in your fetch
420
421
 
@@ -448,11 +449,11 @@ the new filtered result set only has 2 pages. They have to manually click back
448
449
  to page 1. Always reset `pageIndex` to 0 in `onGlobalFilterChange` /
449
450
  `onColumnFiltersChange`.
450
451
 
451
- ### 11. (MEDIUM) Treating `rowModels: {}` as "no row models work"
452
+ ### 11. (MEDIUM) Thinking that omitting all row-model slots means "no row models work"
452
453
 
453
454
  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.
455
+ data array as `Row<...>` objects no matter what — a `features` object with no
456
+ factory slots just means no client-side processing on top.
456
457
 
457
458
  ---
458
459
 
@@ -5,10 +5,10 @@ description: >
5
5
  Key the query on the controlled table state that drives the request (pagination, sorting,
6
6
  filters); use `placeholderData: keepPreviousData` to avoid a "0 rows flash" between pages;
7
7
  set `manualPagination` / `manualSorting` / `manualFiltering` for the slices the server owns;
8
- drop the matching client `rowModels` factories; pass `rowCount` from the server response;
9
- set `getRowId` for stable selection across refetches; hoist controlled slices to Angular
10
- signals + `state` + `on[State]Change`. Alternative `rxResource` / `httpResource` if you
11
- don't want to add the Query dependency (see `client-to-server`).
8
+ drop the matching client row-model factory slots from `features`; pass `rowCount` from the
9
+ server response; set `getRowId` for stable selection across refetches; hoist controlled slices
10
+ to Angular signals + `state` + `on[State]Change`. Alternative: `rxResource` / `httpResource`
11
+ if you don't want to add the Query dependency (see `client-to-server`).
12
12
  type: composition
13
13
  library: tanstack-table
14
14
  framework: angular
@@ -82,6 +82,7 @@ import {
82
82
  tableFeatures,
83
83
  rowPaginationFeature,
84
84
  rowSortingFeature,
85
+ columnFilteringFeature,
85
86
  globalFilteringFeature,
86
87
  createColumnHelper,
87
88
  type ColumnDef,
@@ -92,6 +93,7 @@ import {
92
93
  const features = tableFeatures({
93
94
  rowPaginationFeature,
94
95
  rowSortingFeature,
96
+ columnFilteringFeature,
95
97
  globalFilteringFeature,
96
98
  })
97
99
 
@@ -165,12 +167,11 @@ export class App {
165
167
  }),
166
168
  ]
167
169
 
168
- // 4. Wire the table — manual flags, no client row models, rowCount, getRowId
170
+ // 4. Wire the table — manual flags, features without row-model factories, rowCount, getRowId
169
171
  readonly table = injectTable(() => {
170
172
  const data = this.todosQuery.data() ?? { items: [], totalCount: 0 }
171
173
  return {
172
- features,
173
- rowModels: {}, // ← dropped paginatedRowModel / sortedRowModel / filteredRowModel
174
+ features, // ← features has no paginatedRowModel/sortedRowModel/filteredRowModel slots
174
175
  columns: this.columns,
175
176
  data: data.items,
176
177
  getRowId: (row) => String(row.id),
@@ -219,8 +220,8 @@ For server-driven Table + Query to work correctly:
219
220
  during refetches. Without it, `todosQuery.data()` becomes `undefined`
220
221
  mid-fetch, your table shows 0 rows for a frame, the user notices.
221
222
  3. **`manualPagination` / `manualSorting` / `manualFiltering: true`** for
222
- slices the server owns + **drop the matching `rowModels` factories** so
223
- the table doesn't re-process the data the server already filtered/sorted/paged.
223
+ slices the server owns + **remove the matching factory slots from `features`**
224
+ so the table doesn't re-process the data the server already filtered/sorted/paged.
224
225
  4. **`rowCount: data.totalCount`** (or `pageCount`) so `getPageCount()`
225
226
  computes correctly under `manualPagination`.
226
227
 
@@ -407,15 +408,18 @@ visible until the new one resolves.
407
408
  `getPageCount()` returns `-1`, "next" never disables. The server tells you
408
409
  how many rows exist — pass it.
409
410
 
410
- ### 4. (CRITICAL) Keeping client row models for slices the server owns
411
+ ### 4. (CRITICAL) Keeping client row-model factory slots for slices the server owns
411
412
 
412
413
  ```ts
413
414
  // ❌ Double-processes the data
414
415
  manualPagination: true,
415
- rowModels: { paginatedRowModel: createPaginatedRowModel() }, // re-paginates server page
416
+ // features still has paginatedRowModel slot re-paginates server page
416
417
 
417
- // ✅
418
- rowModels: {} // (or just keep the ones still client-side)
418
+ // ✅ remove the factory slot from features for server-owned slices
419
+ const features = tableFeatures({
420
+ rowPaginationFeature,
421
+ // paginatedRowModel intentionally omitted — server handles it
422
+ })
419
423
  ```
420
424
 
421
425
  Same applies to `sortedRowModel` under `manualSorting` and `filteredRowModel`
@@ -99,7 +99,6 @@ export const sortingStore = new Store<SortingState>([])
99
99
  export class App {
100
100
  readonly table = injectTable(() => ({
101
101
  features,
102
- rowModels: { /* … */ },
103
102
  columns,
104
103
  data: this.data(),
105
104
  atoms: {
@@ -188,9 +187,6 @@ export class PageRoute {
188
187
 
189
188
  readonly table = injectTable(() => ({
190
189
  features,
191
- rowModels: {
192
- /* … */
193
- },
194
190
  columns,
195
191
  data: this.data(),
196
192
  atoms: {
@@ -236,10 +232,23 @@ import { Store } from '@tanstack/store'
236
232
 
237
233
  export const sharedFilter = new Store<string | null>(null)
238
234
 
235
+ const featuresA = tableFeatures({
236
+ columnFilteringFeature,
237
+ globalFilteringFeature,
238
+ filteredRowModel: createFilteredRowModel(),
239
+ filterFns,
240
+ })
241
+
242
+ const featuresB = tableFeatures({
243
+ columnFilteringFeature,
244
+ globalFilteringFeature,
245
+ filteredRowModel: createFilteredRowModel(),
246
+ filterFns,
247
+ })
248
+
239
249
  // Table A
240
250
  readonly tableA = injectTable(() => ({
241
- features: tableFeatures({ globalFilteringFeature }),
242
- rowModels: { filteredRowModel: createFilteredRowModel(filterFns) },
251
+ features: featuresA,
243
252
  columns: columnsA,
244
253
  data: this.dataA(),
245
254
  atoms: { globalFilter: sharedFilter },
@@ -247,8 +256,7 @@ readonly tableA = injectTable(() => ({
247
256
 
248
257
  // Table B (separate component, same module)
249
258
  readonly tableB = injectTable(() => ({
250
- features: tableFeatures({ globalFilteringFeature }),
251
- rowModels: { filteredRowModel: createFilteredRowModel(filterFns) },
259
+ features: featuresB,
252
260
  columns: columnsB,
253
261
  data: this.dataB(),
254
262
  atoms: { globalFilter: sharedFilter },
@@ -82,7 +82,6 @@ export class VirtualTable {
82
82
 
83
83
  readonly table = injectTable(() => ({
84
84
  features,
85
- rowModels: {},
86
85
  columns,
87
86
  data: this.data(),
88
87
  getRowId: (row) => row.id,
@@ -217,7 +216,7 @@ caches that, scrollbar adjusts.)
217
216
  Combine with `rowExpandingFeature` for "click to expand details":
218
217
 
219
218
  - Register `rowExpandingFeature` in `features` and
220
- `expandedRowModel: createExpandedRowModel()` in `rowModels`.
219
+ `expandedRowModel: createExpandedRowModel()` as a slot on `features`.
221
220
  - Use `table.getExpandedRowModel().rows` (or `getRowModel().rows`, which
222
221
  already includes expansion under `paginateExpandedRows: true` semantics —
223
222
  see `tanstack-table/core/row-expanding`).
@@ -2,12 +2,11 @@
2
2
  name: angular/getting-started
3
3
  description: >
4
4
  End-to-end first-table journey for TanStack Table v9 in Angular: install
5
- `@tanstack/angular-table`, declare `features` with `tableFeatures()`, register row-model
6
- factories under `rowModels` with explicit `*Fns` parameters, build columns with the
7
- `TFeatures, TData` generic order, call `injectTable(() => ({...}))` from an injection context,
8
- and render with `FlexRender` / `*flexRenderHeader` / `*flexRenderCell` / `*flexRenderFooter`.
9
- Covers the minimum-viable signal-backed table plus the upgrade path to sorting + filtering +
10
- pagination.
5
+ `@tanstack/angular-table`, declare `features` with `tableFeatures()` (row-model factories and
6
+ fn registries live on the features object), build columns with the `TFeatures, TData` generic
7
+ order, call `injectTable(() => ({...}))` from an injection context, and render with `FlexRender` /
8
+ `*flexRenderHeader` / `*flexRenderCell` / `*flexRenderFooter`. Covers the minimum-viable
9
+ signal-backed table plus the upgrade path to sorting + filtering + pagination.
11
10
  type: lifecycle
12
11
  library: tanstack-table
13
12
  framework: angular
@@ -31,9 +30,9 @@ sources:
31
30
  > Goal: from zero to a working signal-backed, sorted + paginated, type-safe
32
31
  > table in Angular ≥19.
33
32
  >
34
- > v9 is **explicit**: tell the table which features you want with `features`,
35
- > tell it which row models you want with `rowModels`. That explicitness is what
36
- > makes the v9 bundle tree-shakeable.
33
+ > v9 is **explicit**: tell the table which features you want with `features`.
34
+ > Row-model factories and fn registries live on the features object alongside
35
+ > the feature flags. That explicitness is what makes the v9 bundle tree-shakeable.
37
36
 
38
37
  ---
39
38
 
@@ -104,8 +103,7 @@ export class App {
104
103
 
105
104
  // 3. injectTable in an injection context (a class field qualifies)
106
105
  readonly table = injectTable(() => ({
107
- features, // required in v9
108
- rowModels: {}, // {} = core only; that's fine
106
+ features, // required in v9; core row model is automatic
109
107
  columns, // stable ref
110
108
  data: this.data(), // signal read → re-syncs the table on change
111
109
  }))
@@ -156,11 +154,9 @@ driven by the row model.
156
154
  (`getRowModel()`) is always available. With `features: tableFeatures({})`,
157
155
  `table.atoms.*` only contains the slices core ships with — no `pagination`,
158
156
  no `sorting`, no `rowSelection` until you add the matching features.
159
- - `rowModels: {}` does not register any feature-specific row models. Core
160
- row model is included automatically.
161
157
  - `injectTable(() => ({...}))` runs the initializer, builds the table, and
162
158
  re-runs the initializer whenever any signal read inside changes. Stable
163
- references outside the initializer keep `columns` / `features` / `rowModels`
159
+ references outside the initializer keep `columns` / `features`
164
160
  from getting recreated on every data update.
165
161
 
166
162
  ---
@@ -171,7 +167,7 @@ Each opt-in feature has two pieces in v9:
171
167
 
172
168
  1. The **feature** itself (`rowSortingFeature`) in `features` — adds APIs
173
169
  like `column.toggleSorting()` and the `sorting` state slice.
174
- 2. The **row-model factory** (`createSortedRowModel(sortFns)`) in `rowModels`
170
+ 2. The **row-model factory** (`createSortedRowModel()`) on the features object
175
171
  — produces the sorted output. Without it, `table.getRowModel().rows` is
176
172
  unsorted regardless of sort state.
177
173
 
@@ -187,13 +183,12 @@ import {
187
183
 
188
184
  const features = tableFeatures({
189
185
  rowSortingFeature,
186
+ sortedRowModel: createSortedRowModel(), // <-- enables sorting output
187
+ sortFns,
190
188
  })
191
189
 
192
190
  readonly table = injectTable(() => ({
193
191
  features,
194
- rowModels: {
195
- sortedRowModel: createSortedRowModel(sortFns), // <-- enables sorting output
196
- },
197
192
  columns,
198
193
  data: this.data(),
199
194
  }))
@@ -220,7 +215,7 @@ In the template, drive sorting from the header:
220
215
 
221
216
  `sortFns` is the registry of built-in sort functions
222
217
  (`alphanumeric`, `basic`, `datetime`, etc.). Pass only the ones you use to
223
- tree-shake (`createSortedRowModel({ basic: sortFns.basic })`), or pass `sortFns`
218
+ tree-shake (`sortFns: { basic: sortFns.basic }`), or pass `sortFns`
224
219
  in its entirety for all of them.
225
220
 
226
221
  ---
@@ -246,15 +241,15 @@ const features = tableFeatures({
246
241
  rowSortingFeature,
247
242
  columnFilteringFeature,
248
243
  rowPaginationFeature,
244
+ sortedRowModel: createSortedRowModel(),
245
+ filteredRowModel: createFilteredRowModel(),
246
+ paginatedRowModel: createPaginatedRowModel(),
247
+ sortFns,
248
+ filterFns,
249
249
  })
250
250
 
251
251
  readonly table = injectTable(() => ({
252
252
  features,
253
- rowModels: {
254
- sortedRowModel: createSortedRowModel(sortFns),
255
- filteredRowModel: createFilteredRowModel(filterFns),
256
- paginatedRowModel: createPaginatedRowModel(),
257
- },
258
253
  columns,
259
254
  data: this.data(),
260
255
  initialState: {
@@ -323,8 +318,8 @@ const columns = columnHelper.columns([
323
318
  > v9 changed the generic order: `createColumnHelper<typeof features, Person>()`,
324
319
  > **not** `createColumnHelper<Person>()`. Same for `ColumnDef<typeof features, Person>`.
325
320
 
326
- If multiple components share the same `features` / `rowModels`, factor them
327
- into a `createTableHook(...)` call — see
321
+ If multiple components share the same `features` object, factor them into a
322
+ `createTableHook(...)` call — see
328
323
  `tanstack-table/angular/angular-rendering-directives` §10 and the
329
324
  `composable-tables` example.
330
325
 
@@ -338,7 +333,6 @@ row pinning, and refetch-based updates correct.
338
333
  ```ts
339
334
  readonly table = injectTable(() => ({
340
335
  features,
341
- rowModels: { /* … */ },
342
336
  columns,
343
337
  data: this.data(),
344
338
  getRowId: (row) => row.id, // ← stable ID across re-fetches
@@ -360,7 +354,6 @@ and `table.setSorting(...)` to drive updates.
360
354
  ```ts
361
355
  readonly table = injectTable(() => ({
362
356
  features,
363
- rowModels: { /* … */ },
364
357
  columns,
365
358
  data: this.data(),
366
359
  initialState: {
@@ -404,8 +397,8 @@ import { injectTable, tableFeatures } from '@tanstack/angular-table'
404
397
 
405
398
  There is no `getCoreRowModel()` / `getSortedRowModel()` / `getFilteredRowModel()`
406
399
  in v9. Core row model is automatic; the rest are
407
- `createSortedRowModel(sortFns)` / `createFilteredRowModel(filterFns)` / etc.
408
- registered under `rowModels`.
400
+ `createSortedRowModel()` / `createFilteredRowModel()` / etc.
401
+ registered as slots on the `features` object alongside `sortFns` / `filterFns`.
409
402
 
410
403
  ### 3. (CRITICAL) Reimplementing what the table API already does
411
404
 
@@ -424,37 +417,35 @@ The table already does all of this. Use it.
424
417
  ### 4. (HIGH) Feature without its row model (or vice versa)
425
418
 
426
419
  ```ts
427
- // ❌ rowSortingFeature without createSortedRowModel → sort state changes, rows don't reorder
428
- features: tableFeatures({ rowSortingFeature })
429
- rowModels: {
430
- }
420
+ // ❌ rowSortingFeature without sortedRowModel → sort state changes, rows don't reorder
421
+ const features = tableFeatures({ rowSortingFeature })
431
422
 
432
- // ✅
433
- rowModels: {
434
- sortedRowModel: createSortedRowModel(sortFns)
435
- }
423
+ // ✅ factory and fn registry on the features object
424
+ const features = tableFeatures({
425
+ rowSortingFeature,
426
+ sortedRowModel: createSortedRowModel(),
427
+ sortFns,
428
+ })
436
429
  ```
437
430
 
438
431
  Full mapping table → [`references/feature-row-model-mapping.md`](references/feature-row-model-mapping.md).
439
432
 
440
- ### 5. (HIGH) Declaring `columns` / `features` / `rowModels` inside the initializer
433
+ ### 5. (HIGH) Declaring `columns` / `features` inside the initializer
441
434
 
442
435
  ```ts
443
436
  // ❌ Recreated on every signal change
444
437
  readonly table = injectTable(() => ({
445
- features: tableFeatures({ rowSortingFeature }),
446
- rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
438
+ features: tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns }),
447
439
  columns: [/* … */],
448
440
  data: this.data(),
449
441
  }))
450
442
 
451
443
  // ✅ Stable references outside, signal reads inside
452
- const features = tableFeatures({ rowSortingFeature })
444
+ const features = tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns })
453
445
  const columns: Array<ColumnDef<typeof features, Person>> = [/* … */]
454
446
 
455
447
  readonly table = injectTable(() => ({
456
448
  features,
457
- rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
458
449
  columns,
459
450
  data: this.data(),
460
451
  }))
@@ -10,16 +10,19 @@ Every opt-in v9 feature has two pieces:
10
10
 
11
11
  1. The **feature** itself in `features` — adds APIs (e.g.
12
12
  `column.toggleSorting()`) and the matching state slice.
13
- 2. A **row-model factory** in `rowModels` — produces the derived row output.
14
- Without it, sort/filter/paginate UI updates but rows don't reorder.
13
+ 2. A **row-model factory slot** on the `features` object — produces the derived
14
+ row output. Without it, sort/filter/paginate UI updates but rows don't reorder.
15
15
 
16
- | Feature | Row model needed |
16
+ Fn registries (`sortFns`, `filterFns`, `aggregationFns`) are also slots on the
17
+ `features` object, not parameters to the factory calls.
18
+
19
+ | Feature | Slot needed on `features` |
17
20
  | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
18
- | `rowSortingFeature` | `sortedRowModel: createSortedRowModel(sortFns)` |
19
- | `columnFilteringFeature` / `globalFilteringFeature` | `filteredRowModel: createFilteredRowModel(filterFns)` |
21
+ | `rowSortingFeature` | `sortedRowModel: createSortedRowModel()` + `sortFns` |
22
+ | `columnFilteringFeature` / `globalFilteringFeature` | `filteredRowModel: createFilteredRowModel()` + `filterFns` |
20
23
  | `rowPaginationFeature` | `paginatedRowModel: createPaginatedRowModel()` |
21
24
  | `rowExpandingFeature` | `expandedRowModel: createExpandedRowModel()` |
22
- | `columnGroupingFeature` | `groupedRowModel: createGroupedRowModel(aggregationFns)` |
25
+ | `columnGroupingFeature` | `groupedRowModel: createGroupedRowModel()` + `aggregationFns` |
23
26
  | `columnFacetingFeature` | `facetedRowModel: createFacetedRowModel()` (+ `facetedMinMaxValues` / `facetedUniqueValues`) |
24
27
  | `rowSelectionFeature` | (no row model needed) |
25
28
  | `columnVisibilityFeature` / `columnOrderingFeature` / `columnPinningFeature` / `columnSizingFeature` / `columnResizingFeature` / `rowPinningFeature` | (no row model needed) |