This skill builds on @tanstack/table-core#client-vs-server, getting-started, and table-state. Decide the server-owned stages and dataset before wiring Query.
Setup
import {
injectQuery,
keepPreviousData,
} from '@tanstack/angular-query-experimental'
import { signal } from '@angular/core'
import {
injectTable,
rowPaginationFeature,
tableFeatures,
} from '@tanstack/angular-table'
const features = tableFeatures({ rowPaginationFeature })
const EMPTY_ROWS: never[] = []
export class PeopleTable {
readonly pagination = signal({ pageIndex: 0, pageSize: 20 })
readonly query = injectQuery(() => ({
queryKey: [
'people',
this.pagination().pageIndex,
this.pagination().pageSize,
],
queryFn: () =>
fetch(
/api/people?page=${this.pagination().pageIndex}&size=${this.pagination().pageSize},
).then((r) => r.json()),
placeholderData: keepPreviousData,
}))
readonly table = injectTable(() => ({
features,
columns,
data: this.query.data()?.rows ?? EMPTY_ROWS,
rowCount: this.query.data()?.rowCount ?? 0,
manualPagination: true,
state: { pagination: this.pagination() },
onPaginationChange: (next) =>
typeof next === 'function'
? this.pagination.update(next)
: this.pagination.set(next),
}))
}
## Core Patterns
### Track every server-owned input
Read pagination, sorting, and filtering signals inside `injectQuery(() => ...)` and include them in the query key. Return data already processed for each manual stage.
### Keep Query data authoritative
Read the Query signal directly in `injectTable`. Create another signal only for a deliberate editable draft with an explicit cache synchronization policy.
## Common Mistakes
### HIGH Capturing query inputs outside tracking
Wrong:
```ts
const page = this.pagination().pageIndex
readonly query = injectQuery(() => ({ queryKey: ['people', page], queryFn }))
Correct:
readonly query = injectQuery(() => ({ queryKey: ['people', this.pagination().pageIndex], queryFn }))
Signal reads inside the options function establish refetch dependencies.
Source: examples/angular/with-tanstack-query/src/app/app.ts
HIGH Expecting manual mode to request
Wrong:
injectTable(() => ({ features, columns, data, manualPagination: true }))
Correct:
injectTable(() => ({
features,
columns,
data: this.query.data()?.rows ?? EMPTY_ROWS,
manualPagination: true,
}))
Manual flags only bypass client row processing; Query performs network work.
Source: examples/angular/with-tanstack-query/src/app/app.ts
HIGH Omitting total counts
Wrong:
{ data: this.query.data()?.rows ?? EMPTY_ROWS, manualPagination: true }
Correct:
{ data: this.query.data()?.rows ?? EMPTY_ROWS, rowCount: this.query.data()?.rowCount ?? 0, manualPagination: true }
Table needs rowCount or pageCount to constrain navigation across server pages.
Source: docs/framework/angular/guide/pagination.md
API Discovery
Inspect installed @tanstack/angular-table/dist/types/, the relevant core feature source, and installed Angular Query source for the exact injectQuery package/version contract.