Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8b4d12e
feat(angular): wip add table context and hook helpers
riccardoperra Jan 8, 2026
6e77de3
ci: apply automated fixes
autofix-ci[bot] Jan 8, 2026
8a51b52
update composable table example
riccardoperra Jan 8, 2026
2df745b
add some directives to provide table context
riccardoperra Jan 8, 2026
c085061
remove lazyInit set property workaround
riccardoperra Jan 8, 2026
1b4508b
ci: apply automated fixes
autofix-ci[bot] Jan 8, 2026
2dd1df3
fix garbage collection issues in angular
riccardoperra Jan 9, 2026
fe87811
fix: avoid creating computed cache for methods that need an object as…
riccardoperra Jan 9, 2026
7bd52bc
refactor toComputed, add some tests
riccardoperra Jan 10, 2026
48c70bd
refactor(injectTable): improve type definitions and enhance reactivit…
riccardoperra Jan 10, 2026
80ccaf5
refactor(reactivity): enhance memoization and improve reactivity checks
riccardoperra Jan 10, 2026
8c8bf84
add table cell contexts
riccardoperra Jan 11, 2026
9c92b40
cleanup
riccardoperra Jan 11, 2026
a024ecc
fixes some examples
riccardoperra Jan 11, 2026
a488757
refactor flexRender to work with signals
riccardoperra Jan 11, 2026
022aab1
refactor flexRender implementation to work with signals, add FlexRend…
riccardoperra Jan 11, 2026
96d9286
cleanup examples
riccardoperra Jan 11, 2026
87128f8
cleanup structure
riccardoperra Jan 11, 2026
6fb4bac
table state selector fix for angular
riccardoperra Jan 11, 2026
9591572
basic app table example
riccardoperra Jan 11, 2026
bc95e00
code review fixes
riccardoperra Jan 11, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
}
16 changes: 16 additions & 0 deletions examples/angular/basic-app-table/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single

[*.md]
max_line_length = off
trim_trailing_whitespace = false
42 changes: 42 additions & 0 deletions examples/angular/basic-app-table/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings

# System files
.DS_Store
Thumbs.db
27 changes: 27 additions & 0 deletions examples/angular/basic-app-table/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Basic

This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2.

## Development server

Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.

## Code scaffolding

Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.

## Build

Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.

## Running unit tests

Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).

## Running end-to-end tests

Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.

## Further help

To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
107 changes: 107 additions & 0 deletions examples/angular/basic-app-table/angular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"basic-app-table": {
"cli": {
"cache": {
"enabled": false
}
},
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"inlineTemplate": true,
"inlineStyle": true,
"skipTests": true,
"style": "scss"
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:resolver": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"outputPath": "dist/basic-app-table",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "basic-app-table:build:production"
},
"development": {
"buildTarget": "basic-app-table:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n",
"options": {
"buildTarget": "basic-app-table:build"
}
}
}
}
},
"cli": {
"analytics": false
}
}
34 changes: 34 additions & 0 deletions examples/angular/basic-app-table/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "tanstack-table-example-angular-basic",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "eslint ./src"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n examples/angular/basic-app-table/package.json

Repository: TanStack/table

Length of output: 1218


🌐 Web query:

angular-eslint compatibility with Angular 21 versions supported

💡 Result:

Short answer: Use angular-eslint 21.x for Angular 21 — the angular-eslint major versions are aligned with Angular (so 21.x targets Angular/CLI v21). Run ng update angular-eslint together with @angular/core and @angular/cli. [1][2]

Sources:
[1] angular-eslint v21 release notes (update Angular dependencies to v21).
[2] angular-eslint README — major-version alignment with Angular/CLI.


Missing ESLint dependency.

The lint script references ESLint, but neither eslint nor any ESLint-related packages are included in devDependencies. The lint command will fail.

🔧 Proposed fix

Add ESLint and Angular ESLint packages to devDependencies:

  "devDependencies": {
    "@angular/build": "^21.0.4",
    "@angular/cli": "^21.0.4",
    "@angular/compiler-cli": "^21.0.6",
+   "@angular-eslint/builder": "^21.0.0",
+   "@angular-eslint/eslint-plugin": "^21.0.0",
+   "@angular-eslint/eslint-plugin-template": "^21.0.0",
+   "@angular-eslint/template-parser": "^21.0.0",
    "@types/jasmine": "~5.1.13",
+   "eslint": "^9.0.0",
    "jasmine-core": "~5.13.0",
    "tslib": "^2.8.1",
    "typescript": "5.9.3"
  }
🤖 Prompt for AI Agents
In @examples/angular/basic-app-table/package.json at line 10, The package.json
"lint" script invokes ESLint but the project lacks ESLint packages in
devDependencies; update package.json devDependencies to include eslint plus the
Angular ESLint packages (e.g., eslint, @angular-eslint/eslint-plugin,
@angular-eslint/eslint-plugin-template, @angular-eslint/template-parser and any
peer packages needed) and then run npm/yarn install so the "lint" script
succeeds; ensure the versions align with the project's Angular/TypeScript
versions and the existing "lint" script remains unchanged.

},
"private": true,
"dependencies": {
"@angular/common": "^21.0.6",
"@angular/compiler": "^21.0.6",
"@angular/core": "^21.0.6",
"@angular/forms": "^21.0.6",
"@angular/platform-browser": "^21.0.6",
"@angular/platform-browser-dynamic": "^21.0.6",
"@angular/router": "^21.0.6",
"@tanstack/angular-table": "^9.0.0-alpha.10",
Comment on lines +14 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n examples/angular/basic-app-table/package.json

Repository: TanStack/table

Length of output: 1218


🌐 Web query:

Angular 21 release date official version

💡 Result:

Angular 21 (v21.0.0) was released on November 19, 2025 (announced publicly Nov 20, 2025). [1][2]

Sources:
[1] Release metadata listing v21.0.0 — GitClear.
[2] InfoWorld report and coverage of the Angular v21 announcement.


🌐 Web query:

@tanstack/angular-table v9 alpha Angular 21 compatibility

💡 Result:

Short answer: Not officially — the Angular adapter is documented for Angular 17 (v8 docs) and the v9 releases are alpha pre-releases (no official Angular‑21 compatibility guarantee). See TanStack docs stating Angular adapter targets Angular 17 [1] and the v9 alpha release notes listing @tanstack/[email protected].* packages (alpha status) [2].

If you need a definitive check for Angular 21, inspect the v9 @tanstack/angular-table package.json peerDependencies on npm/GitHub or test the alpha in a Angular‑21 app (alpha releases may work but are not guaranteed). [1][2]

Sources:

  1. TanStack Table installation (Angular adapter targets Angular 17).
  2. TanStack table v9 alpha release listings (includes @tanstack/[email protected]‑alpha.*).

@tanstack/angular-table v9 alpha does not have official Angular 21 compatibility.

The TanStack Table Angular adapter is documented for Angular 17. While v9 alpha releases may work, there is no official compatibility guarantee for Angular 21. Using ^21.0.6 with ^9.0.0-alpha.10 may cause runtime issues or unexpected behavior. Consider either:

  • Downgrading to Angular 17 if the v9 alpha should be used
  • Waiting for a stable v9 release with documented Angular 21 support
  • Testing thoroughly if staying with this combination
🤖 Prompt for AI Agents
In @examples/angular/basic-app-table/package.json around lines 14 - 21, The
package.json pins Angular 21 packages (e.g., "@angular/core", "@angular/common",
etc.) while using an alpha of TanStack Angular Table ("@tanstack/angular-table":
"^9.0.0-alpha.10") which lacks official Angular 21 support; either align
versions by downgrading Angular packages to a supported major (e.g., Angular 17)
or bump the table adapter to a stable release that documents Angular 21
compatibility, and update the "dependencies" entries for "@angular/*" or
"@tanstack/angular-table" accordingly after validating compatibility in local
testing.

"rxjs": "~7.8.2",
"zone.js": "~0.16.0"
},
"devDependencies": {
"@angular/build": "^21.0.4",
"@angular/cli": "^21.0.4",
"@angular/compiler-cli": "^21.0.6",
"@types/jasmine": "~5.1.13",
"jasmine-core": "~5.13.0",
"tslib": "^2.8.1",
"typescript": "5.9.3"
}
}
42 changes: 42 additions & 0 deletions examples/angular/basic-app-table/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<div class="p-2">
<table>
<thead>
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
<tr>
@for (header of headerGroup.headers; track header.id) {
@if (!header.isPlaceholder) {
<th *flexRenderHeader="header; let header">
<div [innerHTML]="header"></div>
</th>
}
}
</tr>
}
</thead>
<tbody>
@for (row of table.getRowModel().rows; track row.id) {
<tr>
@for (cell of row.getAllCells(); track cell.id) {
<td *flexRenderCell="cell; let cell">
<div [innerHTML]="cell"></div>
</td>
}
</tr>
}
</tbody>
<tfoot>
@for (footerGroup of table.getFooterGroups(); track footerGroup.id) {
<tr>
@for (footer of footerGroup.headers; track footer.id) {
<th *flexRenderFooter="footer; let footer">
{{ footer }}
</th>
}
</tr>
}
</tfoot>
</table>

<div class="h-4"></div>
<button (click)="rerender()" class="border p-2">Rerender</button>
</div>
119 changes: 119 additions & 0 deletions examples/angular/basic-app-table/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
import { FlexRender, createTableHook } from '@tanstack/angular-table'

// This example uses the new `createTableHook` method to create a re-usable table hook factory instead of independently
// using the standalone `useTable` hook and `createColumnHelper` method. You can choose to use either way.

// 1. Define what the shape of your data will be for each row
type Person = {
firstName: string
lastName: string
age: number
visits: number
status: string
progress: number
}

// 2. Create some dummy data with a stable reference (this could be an API response stored in useState or similar)
const defaultData: Array<Person> = [
{
firstName: 'tanner',
lastName: 'linsley',
age: 24,
visits: 100,
status: 'In Relationship',
progress: 50,
},
{
firstName: 'tandy',
lastName: 'miller',
age: 40,
visits: 40,
status: 'Single',
progress: 80,
},
{
firstName: 'joe',
lastName: 'dirte',
age: 45,
visits: 20,
status: 'Complicated',
progress: 10,
},
{
firstName: 'kevin',
lastName: 'vandy',
age: 28,
visits: 100,
status: 'Single',
progress: 70,
},
]

// 3. New in V9! Tell the table which features and row models we want to use.
// In this case, this will be a basic table with no additional features
const { injectAppTable, createAppColumnHelper } = createTableHook({
_features: {},
_rowModels: {}, // client-side row models. `Core` row model is now included by default, but you can still override it here
debugTable: true,
})

// 4. Create a helper object to help define our columns
const columnHelper = createAppColumnHelper<Person>()

// 5. Define the columns for your table with a stable reference (in this case, defined statically outside of a react component)
const columns = columnHelper.columns([
// accessorKey method (most common for simple use-cases)
columnHelper.accessor('firstName', {
cell: (info) => info.getValue(),
footer: (info) => info.column.id,
}),
// accessorFn used (alternative) along with a custom id
columnHelper.accessor((row) => row.lastName, {
id: 'lastName',
cell: (info) => `<i>${info.getValue()}</i>`,
header: () => `<span>Last Name</span>`,
footer: (info) => info.column.id,
}),
// accessorFn used to transform the data
columnHelper.accessor((row) => Number(row.age), {
id: 'age',
header: () => 'Age',
cell: (info) => info.renderValue(),
footer: (info) => info.column.id,
}),
columnHelper.accessor('visits', {
header: () => `<span>Visits</span>`,
footer: (info) => info.column.id,
}),
columnHelper.accessor('status', {
header: 'Status',
footer: (info) => info.column.id,
}),
columnHelper.accessor('progress', {
header: 'Profile Progress',
footer: (info) => info.column.id,
}),
])

@Component({
selector: 'app-root',
imports: [FlexRender],
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
readonly data = signal<Array<Person>>(defaultData)

// 6. Create the table instance with the required columns and data.
// Features and row models are already defined in the createTableHook call above
readonly table = injectAppTable(() => ({
columns,
data: this.data(),
// add additional table options here or in the createTableHook call above
}))

rerender() {
this.data.set([...defaultData.sort(() => -1)])
}
Comment on lines +116 to +118
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Array mutation bug: sort() mutates defaultData before spreading.

defaultData.sort(() => -1) mutates the original array in-place, then the spread operator copies the already-mutated array. This means each call to rerender() progressively mutates the module-level constant.

Additionally, the comparator () => -1 doesn't follow the sort contract (should return negative/zero/positive based on element comparison), resulting in implementation-defined ordering.

Suggested fix
 rerender() {
-  this.data.set([...defaultData.sort(() => -1)])
+  this.data.set([...defaultData].sort(() => Math.random() - 0.5))
 }

Or for a simple reverse:

 rerender() {
-  this.data.set([...defaultData.sort(() => -1)])
+  this.data.set([...defaultData].reverse())
 }

Note: This pattern exists in examples/angular/basic/src/app/app.component.ts as well, so it may be intentionally simple for demonstration purposes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rerender() {
this.data.set([...defaultData.sort(() => -1)])
}
rerender() {
this.data.set([...defaultData].sort(() => Math.random() - 0.5))
}
Suggested change
rerender() {
this.data.set([...defaultData.sort(() => -1)])
}
rerender() {
this.data.set([...defaultData].reverse())
}
🤖 Prompt for AI Agents
In @examples/angular/basic-app-table/src/app/app.component.ts around lines 116 -
118, The rerender method mutates the module-level defaultData because
defaultData.sort(() => -1) sorts in-place and uses an invalid comparator; change
rerender to operate on a copy and use a stable operation — e.g. make a shallow
copy of defaultData (via slice or spread) and then reverse or sort with a proper
comparator before calling this.data.set; update the rerender function reference
accordingly to avoid mutating defaultData.

}
Loading
Loading