-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
feat(angular): Add angular table composability #6144
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
base: alpha
Are you sure you want to change the base?
Changes from all commits
8b4d12e
6e77de3
8a51b52
2df745b
c085061
1b4508b
2dd1df3
fe87811
7bd52bc
48c70bd
80ccaf5
8c8bf84
9c92b40
a024ecc
a488757
022aab1
96d9286
87128f8
6fb4bac
9591572
bc95e00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
| } |
| 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 |
| 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 |
| 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. |
| 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 | ||
| } | ||
| } |
| 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" | ||
| }, | ||
| "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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n examples/angular/basic-app-table/package.jsonRepository: TanStack/table Length of output: 1218 🌐 Web query:
💡 Result: Angular 21 (v21.0.0) was released on November 19, 2025 (announced publicly Nov 20, 2025). [1][2] Sources: 🌐 Web query:
💡 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:
@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:
🤖 Prompt for AI Agents |
||
| "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" | ||
| } | ||
| } | ||
| 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> |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Array mutation bug:
Additionally, the comparator 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 📝 Committable suggestion
Suggested change
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
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
lintscript references ESLint, but neithereslintnor any ESLint-related packages are included indevDependencies. 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