Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(angular-table) Angular table state docs #5545

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 117 additions & 94 deletions docs/framework/angular/guide/table-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,51 +62,62 @@ In order to control a particular state, you need to both pass in the correspondi

Let's take filtering, sorting, and pagination as an example in a "manual" server-side data fetching scenario. You can store the filtering, sorting, and pagination state in your own state management, but leave out any other state like column order, column visibility, etc. if your API does not care about those values.

```jsx
readonly columnFilters = Angular.signal<ColumnFiltersState>([]) //no default filters
readonly sorting = Angular.signal<SortingState[]>([
{
id: 'age',
desc: true, //sort by age in descending order by default
}
])
readonly pagination = Angular.signal<PaginationState>({
pageIndex: 0,
pageSize: 15
})

//Use our controlled state values to fetch data
tableQuery = injectQuery(() => ({
queryKey: ['users', columnFilters.value, sorting.value, pagination.value],
queryFn: () => fetchUsers(columnFilters.value, sorting.value, pagination.value),
//...
}))
```ts
import {signal} from '@angular/core';
import {SortingState, ColumnFiltersState, PaginationState} from '@tanstack/angular-table'
import {toObservable} from "@angular/core/rxjs-interop";
import {combineLatest, switchMap} from 'rxjs';

class TableComponent {
readonly columnFilters = signal<ColumnFiltersState>([]) //no default filters
readonly sorting = signal<SortingState[]>([
{
id: 'age',
desc: true, //sort by age in descending order by default
}
])
readonly pagination = signal<PaginationState>({
pageIndex: 0,
pageSize: 15
})

//Use our controlled state values to fetch data
readonly data$ = combineLatest({
filters: toObservable(this.columnFilters),
sorting: toObservable(this.sorting),
pagination: toObservable(this.pagination)
}).pipe(
switchMap(({filters, sorting, pagination}) => fetchData(filters, sorting, pagination))
)
readonly data = toSignal(this.data$);

readonly table = createAngularTable(() => ({
columns: this.columns,
data: this.data(),
//...
state: {
columnFilters: this.columnFilters(), //pass controlled state back to the table (overrides internal state)
sorting: this.sorting(),
pagination: this.pagination(),
},
onColumnFiltersChange: updater => { //hoist columnFilters state into our own state management
updater instanceof Function
? this.columnFilters.update(updater)
: this.columnFilters.set(updater)
},
onSortingChange: updater => {
updater instanceof Function
? this.sorting.update(updater)
: this.sorting.set(updater)
},
onPaginationChange: updater => {
updater instanceof Function
? this.pagination.update(updater)
: this.pagination.set(updater)
},
}))
}

table = createAngularTable(() => ({
columns: this.columns,
data: tableQuery.data(),
//...
state: {
columnFilters: this.columnFilters(), //pass controlled state back to the table (overrides internal state)
sorting: this.sorting(),
pagination: this.pagination(),
},
onColumnFiltersChange: updater => { //hoist columnFilters state into our own state management
updater instanceof Function
? this.columnFilters.update(updater)
: this.columnFilters.set(updater)
},
onSortingChange: updater => {
updater instanceof Function
? this.sorting.update(updater)
: this.sorting.set(updater)
},
onPaginationChange: updater => {
updater instanceof Function
? this.pagination.update(updater)
: this.pagination.set(updater)
},
}))
//...
```

Expand All @@ -116,32 +127,40 @@ Alternatively, you can control the entire table state with the `onStateChange` t

A couple of more tricks may be needed to make this work. If you use the `onStateChange` table option, the initial values of the `state` must be populated with all of the relevant state values for all of the features that you want to use. You can either manually type out all of the initial state values, or use the `table.setOptions` API in a special way as shown below.

```jsx
//create a table instance with default state values
initTable = createAngularTable(() => ({
columns: this.columns,
data: this.data(),
//... Note: `state` values are NOT passed in yet
}))
```ts


sate = Angular.useSignal({
...initTable.initialState, //populate the initial state with all of the default state values from the table instance
pagination: {
pageIndex: 0,
pageSize: 15 //optionally customize the initial pagination state.
class TableComponent {
// create an empty table state, we'll override it later
readonly state = signal({} as TableState);

// create a table instance with default state values
readonly table = createAngularTable(() => ({
columns: this.columns,
data: this.data(),
// our fully controlled state overrides the internal state
state: this.state(),
onStateChange: updater => {
// any state changes will be pushed up to our own state management
this.state.set(
updater instanceof Function ? updater(this.state()) : updater
)
}
}))

constructor() {
// set the initial table state
this.state.set({
// populate the initial state with all of the default state values
// from the table instance
...this.table.initialState,
pagination: {
pageIndex: 0,
pageSize: 15, // optionally customize the initial pagination state.
},
})
}
})

//Use the table.setOptions API to merge our fully controlled state onto the table instance
//TODO - how to do this in Angular class?
// table = initTable.setOptions(prev => ({
// ...prev, //preserve any other options that we have set up above
// state: state.value, //our fully controlled state overrides the internal state
// onStateChange: updater => {
// state.value = updater instanceOf Function ? updater(state.value) : updater //any state changes will be pushed up to our own state management
// },
// }))
}
```

### On State Change Callbacks
Expand All @@ -152,22 +171,24 @@ So far, we have seen the `on[State]Change` and `onStateChange` table options wor

Specifying an `on[State]Change` callback tells the table instance that this will be a controlled state. If you do not specify the corresponding `state` value, that state will be "frozen" with its initial value.

```jsx
sorting = Angular.useSignal([])
//...
table = createAngularTable(() => ({
columns: this.columns,
data: this.data(),
//...
state: {
sorting: this.sorting(), //required because we are using `onSortingChange`
},
onSortingChange: updater => { // makes the `state.sorting` controlled
updater instanceof Function
? this.sorting.update(updater)
: this.sorting.set(updater)
}
}))
```ts
class TableComponent {
sorting = signal<SortingState>([])

table = createAngularTable(() => ({
columns: this.columns,
data: this.data(),
//...
state: {
sorting: this.sorting(), // required because we are using `onSortingChange`
},
onSortingChange: updater => { // makes the `state.sorting` controlled
updater instanceof Function
? this.sorting.update(updater)
: this.sorting.set(updater)
}
}))
}
```

#### 2. **Updaters can either be raw values or callback functions**.
Expand All @@ -176,19 +197,21 @@ The `on[State]Change` and `onStateChange` callbacks work exactly like the `setSt

What implications does this have? It means that if you want to add in some extra logic in any of the `on[State]Change` callbacks, you can do so, but you need to check whether or not the new incoming updater value is a function or value.

This is why you will see the `updater instanceOf Function ? this.state.update(updater) : this.state.set(updater)` pattern in the examples above. This pattern checks if the updater is a function, and if it is, it calls the function with the previous state value to get the new state value.
This is why you will see the `updater instanceof Function ? this.state.update(updater) : this.state.set(updater)` pattern in the examples above. This pattern checks if the updater is a function, and if it is, it calls the function with the previous state value to get the new state value.

### State Types

All complex states in TanStack Table have their own TypeScript types that you can import and use. This can be handy for ensuring that you are using the correct data structures and properties for the state values that you are controlling.

```tsx
import { createAngularTable, SortingState } from '@tanstack/angular-table'
//...
const sorting = Angular.useSignal<SortingState[]>([
{
id: 'age', //you should get autocomplete for the `id` and `desc` properties
desc: true,
}
])
```
```ts
import {createAngularTable, SortingState} from '@tanstack/angular-table'

class TableComponent {
readonly sorting = signal<SortingState>([
{
id: 'age', // you should get autocomplete for the `id` and `desc` properties
desc: true,
}
])
}
```
Loading