Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
337 changes: 21 additions & 316 deletions docs/config.json

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,6 @@ export default function MyComponent() {
}
```

### React Forget

When React Forget is released, these problems might be a thing of the past. Or just use Solid.js... 🤓

## How do I stop my table state from automatically resetting when my data changes?

Most plugins use state that _should_ normally reset when the data sources changes, but sometimes you need to suppress that from happening if you are filtering your data externally, or immutably editing your data while looking at it, or simply doing anything external with your data that you don't want to trigger a piece of table state to reset automatically.
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/angular/angular-table.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export class App {
}
```

See the [Table Composition Guide](./guide/table-composition.md) and the [Composable Tables example](./examples/composable-tables) for the full pattern.
See the [Composable Tables Guide](./guide/composable-tables.md) and the [Composable Tables example](./examples/composable-tables) for the full pattern.

## API Reference

Expand Down
184 changes: 184 additions & 0 deletions docs/framework/angular/guide/composable-tables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
---
title: Composable Tables Guide
---

Composable tables are app-level table factories built with `createTableHook`. Instead of repeating the same features, row models, default options, and table/cell/header components in every Angular table, you define that shared infrastructure once and consume it from each table component.

Use this pattern when multiple tables in an Angular app share behavior or rendering conventions. For a single isolated table, `injectTable` is usually enough.

## Examples

- [Composable Tables](../examples/composable-tables) - Two tables sharing one app table setup from `src/app/table.ts`.
- [Basic App Table](../examples/basic-app-table) - Minimal `createTableHook` usage without the larger component registry.

## Setup

The composable tables example keeps the shared setup in `src/app/table.ts`. That file creates one app-specific table factory and exports the helpers used by the rest of the example.

```ts
import {
columnFilteringFeature,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
createTableHook,
filterFns,
rowPaginationFeature,
rowSortingFeature,
sortFns,
tableFeatures,
} from '@tanstack/angular-table'

import {
PaginationControls,
RowCount,
TableToolbar,
} from './components/table-components'
import {
CategoryCell,
NumberCell,
PriceCell,
ProgressCell,
RowActionsCell,
StatusCell,
TextCell,
} from './components/cell-components'
import {
ColumnFilter,
FooterColumnId,
FooterSum,
SortIndicator,
} from './components/header-components'

export const {
createAppColumnHelper,
injectAppTable,
injectTableContext,
injectTableCellContext,
injectTableHeaderContext,
} = createTableHook({
features: tableFeatures({
columnFilteringFeature,
rowPaginationFeature,
rowSortingFeature,
}),
rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
filteredRowModel: createFilteredRowModel(filterFns),
paginatedRowModel: createPaginatedRowModel(),
},
getRowId: (row) => row.id,
tableComponents: {
PaginationControls,
RowCount,
TableToolbar,
},
cellComponents: {
TextCell,
NumberCell,
ProgressCell,
StatusCell,
CategoryCell,
PriceCell,
RowActionsCell,
},
headerComponents: {
SortIndicator,
ColumnFilter,
FooterColumnId,
FooterSum,
},
})
```

This file is the source of truth for the feature set, row model pipeline, row IDs, and registered components used by both tables in the example.

## Returned Helpers

| Helper | Purpose |
|---|---|
| `injectAppTable` | Creates a table with the app's shared `features`, `rowModels`, defaults, and registered components already attached. |
| `createAppColumnHelper` | Creates column helpers where `cell`, `header`, and `footer` contexts know about the registered components. |
| `injectTableContext` | Reads the current table inside registered table components like `PaginationControls`. |
| `injectTableCellContext` | Reads the current cell inside registered cell components like `TextCell`. |
| `injectTableHeaderContext` | Reads the current header/footer inside registered header components like `SortIndicator`. |

## Columns

Use `createAppColumnHelper<TData>()` instead of the base column helper when column definitions should render registered components.

```ts
import { flexRenderComponent } from '@tanstack/angular-table'
import { createAppColumnHelper } from '../../table'
import type { Person } from '../../makeData'

const personColumnHelper = createAppColumnHelper<Person>()

readonly columns = personColumnHelper.columns([
personColumnHelper.accessor('firstName', {
header: 'First Name',
footer: ({ header }) => flexRenderComponent(header.FooterColumnId),
cell: ({ cell }) => flexRenderComponent(cell.TextCell),
}),
personColumnHelper.accessor('age', {
header: 'Age',
footer: ({ header }) => flexRenderComponent(header.FooterSum),
cell: ({ cell }) => flexRenderComponent(cell.NumberCell),
}),
])
```

The registered components are available through the enhanced `cell` and `header` objects because the column helper is bound to the `createTableHook` configuration.

## Table Rendering

Create each table with `injectAppTable`. Per-table options provide the data and columns; shared features and row models come from `src/app/table.ts`.

```ts
table = injectAppTable(() => ({
key: 'users-table',
columns: this.columns,
data: this.data(),
debugTable: true,
}))
```

The Angular table instance is augmented with:

- `table.PaginationControls`, `table.RowCount`, and `table.TableToolbar`
- `table.appCell(cell)` for enhanced cell component types in templates
- `table.appHeader(header)` for enhanced header component types in templates
- `table.appFooter(footer)` for enhanced footer component types in templates

Registered table components can access the table through Angular DI:

```ts
export class PaginationControls {
readonly table = injectTableContext()
}
```

In templates, use the Angular rendering helpers with the app wrappers:

```html
@for (_header of headerGroup.headers; track _header.id) {
@let header = table.appHeader(_header);

<th (click)="header.column.getToggleSortingHandler()?.($event)">
<ng-container *flexRenderHeader="header; let value">
{{ value }}
</ng-container>
<ng-container
*flexRender="header.SortIndicator; props: header.getContext(); let value"
>
{{ value }}
</ng-container>
</th>
}
```

## Reusing The Hook

The example has separate Users and Products table components. Both import `createAppColumnHelper` and `injectAppTable` from `src/app/table.ts`, so they share sorting, filtering, pagination, row IDs, toolbar controls, cell renderers, and header/footer renderers while keeping their own data and columns.

If different product areas need incompatible defaults, create another `createTableHook` setup file and export a second set of app helpers from there.
2 changes: 1 addition & 1 deletion docs/framework/angular/guide/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ const { injectAppTable } = createTableHook(sharedOptions)

For applications with multiple tables sharing the same configuration, `createTableHook` lets you define features, row models, and reusable components once.

For full setup and patterns, see the [Table composition Guide](./table-composition.md).
For full setup and patterns, see the [Composable Tables Guide](./composable-tables.md).

---

Expand Down
Loading
Loading