Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/angular/basic-app-table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"@angular/forms": "^21.2.13",
"@angular/platform-browser": "^21.2.13",
"@faker-js/faker": "^10.4.0",
"@tanstack/angular-devtools": "^0.0.4",
"@tanstack/angular-table": "^9.0.0-alpha.49",
"@tanstack/angular-table-devtools": "^9.0.0-alpha.43",
"rxjs": "~7.8.2",
"tslib": "^2.8.1"
},
Expand Down
20 changes: 18 additions & 2 deletions examples/angular/basic-app-table/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { provideBrowserGlobalErrorListeners } from '@angular/core'
import { isDevMode, provideBrowserGlobalErrorListeners } from '@angular/core'
import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider'
import type { ApplicationConfig } from '@angular/core'

export const appConfig: ApplicationConfig = {
providers: [provideBrowserGlobalErrorListeners()],
providers: [
provideBrowserGlobalErrorListeners(),
isDevMode()
? provideTanStackDevtools(() => ({
plugins: [
{
name: 'TanStack Table',
render: () =>
import('@tanstack/angular-table-devtools').then((m) =>
m.TableDevtoolsPanel(),
),
},
],
}))
: [],
],
}
32 changes: 20 additions & 12 deletions examples/angular/basic-app-table/src/app/app.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
<div class="demo-root">
<button (click)="refreshData()" class="demo-button">Regenerate Data</button>
<button (click)="stressTest()" class="demo-button">Stress Test (1k rows)</button>
<div class="spacer-md"></div>
<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>
}
<th>
@if (!header.isPlaceholder) {
<ng-container *flexRenderHeader="header; let headerCell">
{{ headerCell }}
</ng-container>
}
</th>
}
</tr>
}
Expand All @@ -20,8 +19,10 @@
@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>
<ng-container *flexRenderCell="cell; let renderCell">
{{ renderCell }}
</ng-container>
</td>
}
</tr>
Expand All @@ -31,12 +32,19 @@
@for (footerGroup of table.getFooterGroups(); track footerGroup.id) {
<tr>
@for (footer of footerGroup.headers; track footer.id) {
<th *flexRenderFooter="footer; let footer">
{{ footer }}
<th>
@if (!footer.isPlaceholder) {
<ng-container *flexRenderFooter="footer; let footerCell">
{{ footerCell }}
</ng-container>
}
</th>
}
</tr>
}
</tfoot>
</table>
<div class="spacer-md"></div>
<button (click)="rerender()" class="demo-button">Rerender</button>
<span class="demo-note">Render count: {{ renderCount() }}</span>
</div>
83 changes: 68 additions & 15 deletions examples/angular/basic-app-table/src/app/app.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,86 @@
import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
import { FlexRender, createTableHook } from '@tanstack/angular-table'
import { makeData } from './makeData'
import type { Person } from './makeData'
import { injectTanStackTableDevtools } from '@tanstack/angular-table-devtools'

// This example uses `createTableHook` to create a reusable Angular table helper instead of independently using `injectTable` and `createColumnHelper`.

// 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
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
_rowModels: {},
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)
// 5. Define the columns for your table with a stable reference
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>`,
cell: (info) => info.getValue(),
header: () => 'Last Name',
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>`,
header: () => 'Visits',
footer: (info) => info.column.id,
}),
columnHelper.accessor('status', {
Expand All @@ -56,16 +100,25 @@ const columns = columnHelper.columns([
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
readonly data = signal<Array<Person>>(makeData(20))
// 6. Store data with a stable reference
readonly data = signal<Array<Person>>([...defaultData])
readonly renderCount = signal(0)

// 6. Create the table instance with the required columns and data.
// 7. 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(() => ({
debugTable: true,
columns,
data: this.data(),
// add additional table options here or in the createTableHook call above
}))

refreshData = () => this.data.set(makeData(20))
stressTest = () => this.data.set(makeData(1_000))
rerender() {
this.renderCount.update((count) => count + 1)
}
constructor() {
injectTanStackTableDevtools(() => ({
table: this.table,
name: 'basic-app-table',
}))
}
}
48 changes: 48 additions & 0 deletions examples/angular/basic-external-atoms/angular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": { "enabled": false }
},
"newProjectRoot": "projects",
"projects": {
"basic-external-atoms": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": [],
"styles": ["src/styles.css"]
},
"configurations": {
"production": {
"budgets": [
{ "type": "initial", "maximumWarning": "500kB", "maximumError": "1MB" },
{ "type": "anyComponentStyle", "maximumWarning": "4kB", "maximumError": "8kB" }
],
"outputHashing": "all"
},
"development": { "optimization": false, "extractLicenses": false, "sourceMap": true }
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": { "buildTarget": "basic-external-atoms:build:production" },
"development": { "buildTarget": "basic-external-atoms:build:development" }
},
"defaultConfiguration": "development"
}
}
}
}
}
31 changes: 31 additions & 0 deletions examples/angular/basic-external-atoms/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "tanstack-angular-table-example-basic-external-atoms",
"scripts": {
"ng": "ng",
"start": "ng serve --port 5173",
"dev": "ng serve --port 5173",
"build": "ng build",
"lint": "eslint ./src",
"watch": "ng build --watch --configuration development"
},
"private": true,
"packageManager": "pnpm@11.1.1",
"dependencies": {
"@angular/common": "^21.2.12",
"@angular/compiler": "^21.2.12",
"@angular/core": "^21.2.12",
"@angular/forms": "^21.2.12",
"@angular/platform-browser": "^21.2.12",
"@faker-js/faker": "^10.4.0",
"@tanstack/angular-store": "^0.11.0",
"@tanstack/angular-table": "^9.0.0-alpha.46",
"rxjs": "~7.8.2",
"tslib": "^2.8.1"
},
"devDependencies": {
"@angular/build": "^21.2.11",
"@angular/cli": "^21.2.11",
"@angular/compiler-cli": "^21.2.12",
"typescript": "6.0.3"
}
}
6 changes: 6 additions & 0 deletions examples/angular/basic-external-atoms/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { provideBrowserGlobalErrorListeners } from '@angular/core'
import type { ApplicationConfig } from '@angular/core'

export const appConfig: ApplicationConfig = {
providers: [provideBrowserGlobalErrorListeners()],
}
53 changes: 53 additions & 0 deletions examples/angular/basic-external-atoms/src/app/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<div class="demo-root">
<div>
<button (click)="refreshData()" class="demo-button">Regenerate Data</button>
<button (click)="stressTest()" class="demo-button">Stress Test (200k rows)</button>
</div>
<div class="spacer-md"></div>
<table>
<thead>
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
<tr>
@for (header of headerGroup.headers; track header.id) {
<th [attr.colSpan]="header.colSpan">
@if (!header.isPlaceholder) {
<div [class.sortable-header]="header.column.getCanSort()" (click)="header.column.getToggleSortingHandler()?.($event)">
<ng-container *flexRenderHeader="header; let headerCell">{{ headerCell }}</ng-container>
@if (header.column.getIsSorted() === 'asc') { 🔼 }
@if (header.column.getIsSorted() === 'desc') { 🔽 }
</div>
}
</th>
}
</tr>
}
</thead>
<tbody>
@for (row of table.getRowModel().rows; track row.id) {
<tr>
@for (cell of row.getAllCells(); track cell.id) {
<td><ng-container *flexRenderCell="cell; let renderCell">{{ renderCell }}</ng-container></td>
}
</tr>
}
</tbody>
</table>
<div class="spacer-sm"></div>
<div class="controls">
<button class="demo-button demo-button-sm" (click)="table.setPageIndex(0)" [disabled]="!table.getCanPreviousPage()"><<</button>
<button class="demo-button demo-button-sm" (click)="table.previousPage()" [disabled]="!table.getCanPreviousPage()"><</button>
<button class="demo-button demo-button-sm" (click)="table.nextPage()" [disabled]="!table.getCanNextPage()">></button>
<button class="demo-button demo-button-sm" (click)="table.setPageIndex(table.getPageCount() - 1)" [disabled]="!table.getCanNextPage()">>></button>
Comment on lines +37 to +40
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="examples/angular/basic-external-atoms/src/app/app.html"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

nl -ba "$FILE" | sed -n '30,50p'

Repository: TanStack/table

Length of output: 102


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="examples/angular/basic-external-atoms/src/app/app.html"

if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "=== Lines 30-50 (with line numbers) ==="
# Use cat -n (available) to show line numbers
cat -n "$FILE" | sed -n '30,50p'

echo
echo "=== Raw pagination text searches ==="
rg -n --fixed-strings '<<' "$FILE" || true
rg -n --fixed-strings '<' "$FILE" || true
rg -n --fixed-strings '>' "$FILE" || true
rg -n --fixed-strings '>>' "$FILE" || true

Repository: TanStack/table

Length of output: 6700


Escape pagination arrow characters in Angular button text (HTML entities required).

examples/angular/basic-external-atoms/src/app/app.html lines 37-40 contain raw </> characters in button text (<<, <, >, >>). These should be escaped to avoid Angular/HTML parsing/lint issues and to ensure the intended glyphs render.

Proposed fix
-    <button class="demo-button demo-button-sm" (click)="table.setPageIndex(0)" [disabled]="!table.getCanPreviousPage()"><<</button>
-    <button class="demo-button demo-button-sm" (click)="table.previousPage()" [disabled]="!table.getCanPreviousPage()"><</button>
-    <button class="demo-button demo-button-sm" (click)="table.nextPage()" [disabled]="!table.getCanNextPage()">></button>
-    <button class="demo-button demo-button-sm" (click)="table.setPageIndex(table.getPageCount() - 1)" [disabled]="!table.getCanNextPage()">>></button>
+    <button class="demo-button demo-button-sm" (click)="table.setPageIndex(0)" [disabled]="!table.getCanPreviousPage()">&lt;&lt;</button>
+    <button class="demo-button demo-button-sm" (click)="table.previousPage()" [disabled]="!table.getCanPreviousPage()">&lt;</button>
+    <button class="demo-button demo-button-sm" (click)="table.nextPage()" [disabled]="!table.getCanNextPage()">&gt;</button>
+    <button class="demo-button demo-button-sm" (click)="table.setPageIndex(table.getPageCount() - 1)" [disabled]="!table.getCanNextPage()">&gt;&gt;</button>
🧰 Tools
🪛 HTMLHint (1.9.2)

[error] 37-37: Special characters must be escaped : [ < ].

(spec-char-escape)


[error] 37-37: Special characters must be escaped : [ < ].

(spec-char-escape)


[error] 38-38: Special characters must be escaped : [ < ].

(spec-char-escape)


[error] 39-39: Special characters must be escaped : [ > ].

(spec-char-escape)


[error] 40-40: Special characters must be escaped : [ > ].

(spec-char-escape)


[error] 40-40: Special characters must be escaped : [ > ].

(spec-char-escape)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/angular/basic-external-atoms/src/app/app.html` around lines 37 - 40,
The button labels currently include raw "<" and ">" characters which must be
escaped in the template to avoid HTML/Angular parsing issues; update the four
button text nodes that call table.setPageIndex(0), table.previousPage(),
table.nextPage(), and table.setPageIndex(table.getPageCount() - 1) to use HTML
entities (e.g. replace "<<" and "<" with "&lt;&lt;" and "&lt;", and ">" and ">>"
with "&gt;" and "&gt;&gt;") while leaving the (click) handlers and [disabled]
bindings (table.getCanPreviousPage(), table.getCanNextPage(),
table.getPageCount()) unchanged.

<span class="inline-controls"><div>Page</div><strong>{{ (pagination().pageIndex + 1).toLocaleString() }} of {{ table.getPageCount().toLocaleString() }}</strong></span>
<span class="inline-controls">| Go to page: <input type="number" min="1" [max]="table.getPageCount()" [value]="pagination().pageIndex + 1" (input)="onPageInputChange($event)" class="page-size-input" /></span>
<select [value]="pagination().pageSize" (change)="onPageSizeChange($event)">
@for (pageSize of [10, 20, 30, 40, 50]; track pageSize) {
<option [value]="pageSize">Show {{ pageSize }}</option>
}
</select>
</div>
<div class="spacer-md"></div>
<button (click)="rerender()" class="demo-button">Rerender</button>
<span>Render count: {{ renderCount() }}</span>
<pre>{{ stateJson() }}</pre>
</div>
Loading
Loading