@tanstack/angular-table
Version:
Headless UI for building powerful tables & datagrids for Angular.
198 lines (141 loc) • 6.44 kB
Markdown
---
name: table-state
description: >
Use Angular-signal-backed table.atoms, direct template reads, computed selectors, controlled signals, value-or-updater callbacks, and external Angular Store atoms while accounting for injectTable initializer reruns.
metadata:
type: framework
library: '@tanstack/angular-table'
framework: angular
library_version: '9.1.2'
requires:
- '@tanstack/table-core#core'
- getting-started
sources:
- 'TanStack/table:docs/framework/angular/guide/table-state.md'
- 'TanStack/table:examples/angular/basic-external-state'
- 'TanStack/table:packages/angular-table/src/injectTable.ts'
---
This skill builds on `@tanstack/table-core#core` and `getting-started`. Read them first for state ownership and Angular construction.
## State Mental Model
TanStack Table is primarily a state coordinator. Keep state internal unless another system must read, persist, or drive it. Without `initialState`, `atoms`, `state`, or `on[State]Change`, the table owns all registered slices.
- `table.baseAtoms` are internal writable atoms initialized from resolved initial state.
- `table.atoms` are readonly derived atoms for the active owner of each registered slice.
- `table.store` combines those atoms into one readonly flat store.
The Angular adapter backs atoms with Angular signals. Reads participate in tracking inside templates, `computed`, and `effect`; signal reads inside the `injectTable` initializer also rerun that initializer and call `setOptions`. State is feature-based: missing pagination state or APIs indicate a missing `rowPaginationFeature`. Hoist stable `features` and `columns` outside the initializer, and return signal-backed `data` without mapping or slicing inline.
## Setup
```ts
import { computed, signal } from '@angular/core'
import {
injectTable,
rowPaginationFeature,
tableFeatures,
} from '@tanstack/angular-table'
const features = tableFeatures({ rowPaginationFeature })
const columns = [{ accessorKey: 'name' }]
export class TableComponent {
readonly data = signal([{ name: 'Ada' }])
readonly table = injectTable(() => ({ features, columns, data: this.data() }))
readonly pageIndex = computed(
() => this.table.atoms.pagination.get().pageIndex,
)
}
```
Angular-backed table atom reads are signal reads. Templates, `computed`, and `effect` track them directly.
## Core Patterns
### Prefer external atoms for cross-system state
```ts
import { createAtom } from '@tanstack/angular-store'
import type { PaginationState } from '@tanstack/angular-table'
readonly paginationAtom = createAtom<PaginationState>({ pageIndex: 0, pageSize: 20 })
readonly table = injectTable(() => ({
features, columns, data: this.data(), atoms: { pagination: this.paginationAtom },
}))
```
The atom can feed Query without making every state write rerun the Table initializer.
### Resolve controlled signal updaters
```ts
readonly pagination = signal({ pageIndex: 0, pageSize: 20 })
readonly table = injectTable(() => ({
features, columns, data: this.data(), state: { pagination: this.pagination() },
onPaginationChange: next => typeof next === 'function' ? this.pagination.update(next) : this.pagination.set(next),
}))
```
## Choose State Ownership
Use one owner for each slice:
- Prefer internal state and feature APIs for local interaction.
- Use `initialState` for starting/reset values; later changes do not reset current state.
- Prefer a stable atom from `@tanstack/angular-store` in `atoms` for Query or other cross-system state. Table APIs update it without a change callback.
- Use an Angular signal read in `state.<slice>` plus the matching callback for simple controlled state. Handle raw values and updater functions.
External atoms win over controlled `state`, which syncs into the internal base atom. Do not give a slice two owners. The global v8 `onStateChange` option is gone; subscribe to `table.store` when all state changes must be observed.
## Initialize, Update, and Reset
Prefer feature methods such as `setSorting`, `nextPage`, `toggleVisibility`, and `toggleSelected`. Direct base-atom writes are a rare escape hatch for internal state; write the external atom when it owns a slice.
```ts
this.table.resetSorting()
this.table.resetPagination()
this.table.resetPagination(true)
```
Feature resets use `table.initialState` unless `true` requests the feature default, and can update external owners. Core `table.reset()` resets internal base atoms only. Use feature-specific types such as `PaginationState`; use `TableState<typeof features>` when the complete registered state type is needed.
## Common Mistakes
### MEDIUM Wrapping atoms redundantly
Wrong:
```ts
readonly pagination = computed(() => computed(() => this.table.atoms.pagination.get())())
```
Correct:
```ts
readonly pagination = computed(() => this.table.atoms.pagination.get())
```
Table atoms already bridge to Angular signals; one tracked read is sufficient.
Source: `docs/framework/angular/guide/table-state.md`
### HIGH Ignoring initializer reruns
Wrong:
```ts
injectTable(() => ({
features: tableFeatures({ rowPaginationFeature }),
columns: makeColumns(),
state: { pagination: this.pagination() },
data,
}))
```
Correct:
```ts
injectTable(() => ({
features,
columns,
state: { pagination: this.pagination() },
data,
}))
```
Controlled signal writes rerun the initializer, so static work must remain outside it.
Source: `packages/angular-table/src/injectTable.ts`
### HIGH Storing updater functions
Wrong:
```ts
onPaginationChange: (next) => this.pagination.set(next)
```
Correct:
```ts
onPaginationChange: (next) =>
typeof next === 'function'
? this.pagination.update(next)
: this.pagination.set(next)
```
Callbacks receive a value or updater; assigning the function corrupts owned state.
Source: `examples/angular/basic-external-state/src/app/app.ts`
### MEDIUM Giving one slice multiple owners
Wrong:
```ts
{ initialState: { pagination: start }, atoms: { pagination: this.paginationAtom } }
```
Correct:
```ts
{
atoms: {
pagination: this.paginationAtom
}
}
```
External atoms/state override initial state; choose one owner for each slice.
Source: `docs/framework/angular/guide/table-state.md`
## API Discovery
Inspect `node_modules/@tanstack/angular-table/dist/types/` and `reactivity.d.ts`; inspect `@tanstack/angular-store/dist/` for external atoms and installed core feature source for state APIs.