Skip to content

Commit 099e1a4

Browse files
authoredFeb 3, 2025··
docs(angular): add expanding and sub components examples (#5898)
* docs(angular): add expanding example * docs(angular): add sub components example * docs(angular): fix config.json
1 parent 57703a4 commit 099e1a4

40 files changed

+1367
-1
lines changed
 

Diff for: ‎docs/config.json

+8
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,14 @@
435435
"to": "framework/angular/examples/row-selection",
436436
"label": "Row Selection"
437437
},
438+
{
439+
"to": "framework/angular/examples/expanding",
440+
"label": "Expanding"
441+
},
442+
{
443+
"to": "framework/angular/examples/sub-components",
444+
"label": "Sub Components"
445+
},
438446
{
439447
"to": "framework/angular/examples/signal-input",
440448
"label": "Signal Input"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "Node.js",
3+
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
4+
}

Diff for: ‎examples/angular/expanding/.editorconfig

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Editor configuration, see https://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.ts]
12+
quote_type = single
13+
14+
[*.md]
15+
max_line_length = off
16+
trim_trailing_whitespace = false

Diff for: ‎examples/angular/expanding/.gitignore

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# See http://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# Compiled output
4+
/dist
5+
/tmp
6+
/out-tsc
7+
/bazel-out
8+
9+
# Node
10+
/node_modules
11+
npm-debug.log
12+
yarn-error.log
13+
14+
# IDEs and editors
15+
.idea/
16+
.project
17+
.classpath
18+
.c9/
19+
*.launch
20+
.settings/
21+
*.sublime-workspace
22+
23+
# Visual Studio Code
24+
.vscode/*
25+
!.vscode/settings.json
26+
!.vscode/tasks.json
27+
!.vscode/launch.json
28+
!.vscode/extensions.json
29+
.history/*
30+
31+
# Miscellaneous
32+
/.angular/cache
33+
.sass-cache/
34+
/connect.lock
35+
/coverage
36+
/libpeerconnection.log
37+
testem.log
38+
/typings
39+
40+
# System files
41+
.DS_Store
42+
Thumbs.db

Diff for: ‎examples/angular/expanding/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Basic
2+
3+
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2.
4+
5+
## Development server
6+
7+
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.
8+
9+
## Code scaffolding
10+
11+
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`.
12+
13+
## Build
14+
15+
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16+
17+
## Running unit tests
18+
19+
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20+
21+
## Running end-to-end tests
22+
23+
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.
24+
25+
## Further help
26+
27+
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.

Diff for: ‎examples/angular/expanding/angular.json

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{
2+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3+
"version": 1,
4+
"newProjectRoot": "projects",
5+
"projects": {
6+
"expanding": {
7+
"cli": {
8+
"cache": {
9+
"enabled": false
10+
}
11+
},
12+
"projectType": "application",
13+
"schematics": {
14+
"@schematics/angular:component": {
15+
"style": "scss"
16+
}
17+
},
18+
"root": "",
19+
"sourceRoot": "src",
20+
"prefix": "app",
21+
"architect": {
22+
"build": {
23+
"builder": "@angular-devkit/build-angular:application",
24+
"options": {
25+
"outputPath": "dist/expanding",
26+
"index": "src/index.html",
27+
"browser": "src/main.ts",
28+
"polyfills": ["zone.js"],
29+
"tsConfig": "tsconfig.app.json",
30+
"inlineStyleLanguage": "scss",
31+
"assets": ["src/favicon.ico", "src/assets"],
32+
"styles": ["src/styles.scss"],
33+
"scripts": []
34+
},
35+
"configurations": {
36+
"production": {
37+
"budgets": [],
38+
"outputHashing": "all"
39+
},
40+
"development": {
41+
"optimization": false,
42+
"extractLicenses": false,
43+
"sourceMap": true
44+
}
45+
},
46+
"defaultConfiguration": "production"
47+
},
48+
"serve": {
49+
"builder": "@angular-devkit/build-angular:dev-server",
50+
"configurations": {
51+
"production": {
52+
"buildTarget": "expanding:build:production"
53+
},
54+
"development": {
55+
"buildTarget": "expanding:build:development"
56+
}
57+
},
58+
"defaultConfiguration": "development"
59+
},
60+
"extract-i18n": {
61+
"builder": "@angular-devkit/build-angular:extract-i18n",
62+
"options": {
63+
"buildTarget": "expanding:build"
64+
}
65+
},
66+
"test": {
67+
"builder": "@angular-devkit/build-angular:karma",
68+
"options": {
69+
"polyfills": ["zone.js", "zone.js/testing"],
70+
"tsConfig": "tsconfig.spec.json",
71+
"inlineStyleLanguage": "scss",
72+
"assets": ["src/favicon.ico", "src/assets"],
73+
"styles": ["src/styles.scss"],
74+
"scripts": []
75+
}
76+
}
77+
}
78+
}
79+
},
80+
"cli": {
81+
"analytics": false
82+
}
83+
}

Diff for: ‎examples/angular/expanding/package.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "tanstack-table-example-angular-expanding",
3+
"version": "0.0.0",
4+
"scripts": {
5+
"ng": "ng",
6+
"start": "ng serve",
7+
"build": "ng build",
8+
"watch": "ng build --watch --configuration development",
9+
"test": "ng test"
10+
},
11+
"private": true,
12+
"dependencies": {
13+
"@angular/animations": "^17.3.9",
14+
"@angular/common": "^17.3.9",
15+
"@angular/compiler": "^17.3.9",
16+
"@angular/core": "^17.3.9",
17+
"@angular/forms": "^17.3.9",
18+
"@angular/platform-browser": "^17.3.9",
19+
"@angular/platform-browser-dynamic": "^17.3.9",
20+
"@angular/router": "^17.3.9",
21+
"@faker-js/faker": "^8.4.1",
22+
"@tanstack/angular-table": "^8.21.0",
23+
"rxjs": "~7.8.1",
24+
"zone.js": "~0.14.4"
25+
},
26+
"devDependencies": {
27+
"@angular-devkit/build-angular": "^17.3.8",
28+
"@angular/cli": "^17.3.8",
29+
"@angular/compiler-cli": "^17.3.9",
30+
"tslib": "^2.6.2",
31+
"typescript": "5.4.5"
32+
}
33+
}
+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<div class="p-2">
2+
<div class="h-4"></div>
3+
4+
<div class="overflow-x-auto">
5+
<table>
6+
<thead>
7+
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
8+
<tr>
9+
@for (header of headerGroup.headers; track header.id) {
10+
<th [colSpan]="header.colSpan">
11+
@if (!header.isPlaceholder) {
12+
<ng-container
13+
*flexRender="
14+
header.column.columnDef.header;
15+
props: header.getContext();
16+
let header
17+
"
18+
>
19+
<div [innerHTML]="header"></div>
20+
</ng-container>
21+
}
22+
</th>
23+
}
24+
</tr>
25+
}
26+
</thead>
27+
<tbody>
28+
@for (row of table.getRowModel().rows; track row.id) {
29+
<tr>
30+
@for (cell of row.getVisibleCells(); track cell.id) {
31+
<td>
32+
<ng-container
33+
*flexRender="
34+
cell.column.columnDef.cell;
35+
props: cell.getContext();
36+
let cell
37+
"
38+
>
39+
<div [innerHTML]="cell"></div>
40+
</ng-container>
41+
</td>
42+
}
43+
</tr>
44+
}
45+
</tbody>
46+
</table>
47+
</div>
48+
49+
<div class="h-2"></div>
50+
<div class="flex items-center gap-2">
51+
<button
52+
class="border rounded p-1"
53+
(click)="table.setPageIndex(0)"
54+
[disabled]="!table.getCanPreviousPage()"
55+
>
56+
<<
57+
</button>
58+
<button
59+
class="border rounded p-1"
60+
(click)="table.previousPage()"
61+
[disabled]="!table.getCanPreviousPage()"
62+
>
63+
<
64+
</button>
65+
<button
66+
class="border rounded p-1"
67+
(click)="table.nextPage()"
68+
[disabled]="!table.getCanNextPage()"
69+
>
70+
>
71+
</button>
72+
<button
73+
class="border rounded p-1"
74+
(click)="table.setPageIndex(table.getPageCount() - 1)"
75+
[disabled]="!table.getCanNextPage()"
76+
>
77+
>>
78+
</button>
79+
<span class="flex items-center gap-1">
80+
<div>Page</div>
81+
<strong>
82+
{{ table.getState().pagination.pageIndex + 1 }} of
83+
{{ table.getPageCount() }}
84+
</strong>
85+
</span>
86+
<span class="flex items-center gap-1">
87+
| Go to page:
88+
<input
89+
type="number"
90+
[value]="table.getState().pagination.pageIndex + 1"
91+
(input)="onPageInputChange($event)"
92+
class="border p-1 rounded w-16"
93+
/>
94+
</span>
95+
96+
<select
97+
[value]="table.getState().pagination.pageSize"
98+
(change)="onPageSizeChange($event)"
99+
>
100+
@for (pageSize of [10, 20, 30, 40, 50]; track pageSize) {
101+
<option [value]="pageSize">Show {{ pageSize }}</option>
102+
}
103+
</select>
104+
</div>
105+
106+
<label>Expanded State:</label>
107+
<pre>{{ rawExpandedState() }}</pre>
108+
<label>Row Selection State:</label>
109+
<pre>{{ rawRowSelectionState() }}</pre>
110+
</div>

Diff for: ‎examples/angular/expanding/src/app/app.component.ts

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
computed,
5+
signal,
6+
} from '@angular/core'
7+
import {
8+
ColumnDef,
9+
createAngularTable,
10+
ExpandedState,
11+
flexRenderComponent,
12+
FlexRenderDirective,
13+
getCoreRowModel,
14+
getExpandedRowModel,
15+
getFilteredRowModel,
16+
getPaginationRowModel,
17+
} from '@tanstack/angular-table'
18+
import { makeData, type Person } from './makeData'
19+
import { ReactiveFormsModule } from '@angular/forms'
20+
import { ExpandableCell, ExpandableHeaderCell } from './expandable-cell'
21+
22+
const defaultColumns: ColumnDef<Person>[] = [
23+
{
24+
accessorKey: 'firstName',
25+
header: () =>
26+
flexRenderComponent(ExpandableHeaderCell, {
27+
inputs: {
28+
label: 'First name',
29+
},
30+
}),
31+
cell: () => flexRenderComponent(ExpandableCell),
32+
},
33+
{
34+
accessorFn: row => row.lastName,
35+
id: 'lastName',
36+
cell: info => info.getValue(),
37+
header: () => 'Last Name',
38+
footer: props => props.column.id,
39+
},
40+
{
41+
accessorKey: 'age',
42+
header: () => 'Age',
43+
footer: props => props.column.id,
44+
},
45+
{
46+
accessorKey: 'visits',
47+
header: () => `Visits`,
48+
footer: props => props.column.id,
49+
},
50+
{
51+
accessorKey: 'status',
52+
header: 'Status',
53+
footer: props => props.column.id,
54+
},
55+
{
56+
accessorKey: 'progress',
57+
header: 'Profile Progress',
58+
footer: props => props.column.id,
59+
},
60+
]
61+
62+
@Component({
63+
selector: 'app-root',
64+
standalone: true,
65+
imports: [FlexRenderDirective, ReactiveFormsModule],
66+
templateUrl: './app.component.html',
67+
changeDetection: ChangeDetectionStrategy.OnPush,
68+
})
69+
export class AppComponent {
70+
readonly data = signal<Person[]>(makeData(100, 5, 3))
71+
readonly expanded = signal<ExpandedState>({})
72+
73+
readonly table = createAngularTable(() => ({
74+
data: this.data(),
75+
columns: defaultColumns,
76+
state: {
77+
expanded: this.expanded(),
78+
},
79+
onExpandedChange: updater =>
80+
typeof updater === 'function'
81+
? this.expanded.update(updater)
82+
: this.expanded.set(updater),
83+
getSubRows: row => row.subRows,
84+
getCoreRowModel: getCoreRowModel(),
85+
getPaginationRowModel: getPaginationRowModel(),
86+
getFilteredRowModel: getFilteredRowModel(),
87+
getExpandedRowModel: getExpandedRowModel(),
88+
// filterFromLeafRows: true,
89+
// maxLeafRowFilterDepth: 0,
90+
debugTable: true,
91+
}))
92+
93+
readonly rawExpandedState = computed(() =>
94+
JSON.stringify(this.expanded(), undefined, 2)
95+
)
96+
97+
readonly rawRowSelectionState = computed(() =>
98+
JSON.stringify(this.table.getState().rowSelection, undefined, 2)
99+
)
100+
101+
onPageInputChange(event: Event): void {
102+
const inputElement = event.target as HTMLInputElement
103+
const page = inputElement.value ? Number(inputElement.value) - 1 : 0
104+
this.table.setPageIndex(page)
105+
}
106+
107+
onPageSizeChange(event: any): void {
108+
this.table.setPageSize(Number(event.target.value))
109+
}
110+
}

Diff for: ‎examples/angular/expanding/src/app/app.config.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ApplicationConfig } from '@angular/core'
2+
3+
export const appConfig: ApplicationConfig = {
4+
providers: [],
5+
}
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
2+
import {
3+
type HeaderContext,
4+
injectFlexRenderContext,
5+
type Table,
6+
CellContext,
7+
} from '@tanstack/angular-table'
8+
9+
@Component({
10+
standalone: true,
11+
template: `
12+
<input
13+
type="checkbox"
14+
[indeterminate]="table.getIsSomeRowsSelected()"
15+
[checked]="table.getIsAllRowsSelected()"
16+
(change)="table.getToggleAllRowsSelectedHandler()($event)"
17+
/>
18+
{{ ' ' }}
19+
20+
<button (click)="table.getToggleAllRowsExpandedHandler()($event)">
21+
{{ context.table.getIsAllRowsExpanded() ? '👇' : '👉' }}
22+
</button>
23+
24+
{{ label() }}
25+
`,
26+
changeDetection: ChangeDetectionStrategy.OnPush,
27+
})
28+
export class ExpandableHeaderCell<T> {
29+
readonly context = injectFlexRenderContext<HeaderContext<T, unknown>>()
30+
31+
readonly label = input.required<string>()
32+
33+
get table() {
34+
return this.context.table as Table<T>
35+
}
36+
}
37+
38+
@Component({
39+
standalone: true,
40+
template: `
41+
<div [style.--depth]="row.depth">
42+
<div>
43+
<input
44+
type="checkbox"
45+
[indeterminate]="row.getIsSomeSelected()"
46+
[checked]="row.getIsSelected()"
47+
(change)="row.getToggleSelectedHandler()($event)"
48+
/>
49+
{{ ' ' }}
50+
51+
@if (row.getCanExpand()) {
52+
<button (click)="row.getToggleExpandedHandler()()">
53+
{{ row.getIsExpanded() ? '👇' : '👉' }}
54+
</button>
55+
} @else {
56+
<span>🔵</span>
57+
}
58+
{{ ' ' }}
59+
60+
{{ context.getValue() }}
61+
</div>
62+
</div>
63+
`,
64+
changeDetection: ChangeDetectionStrategy.OnPush,
65+
styles: `
66+
:host {
67+
> div {
68+
padding-left: calc(2rem * var(--depth, 1));
69+
}
70+
}
71+
`,
72+
})
73+
export class ExpandableCell<T> {
74+
readonly context = injectFlexRenderContext<CellContext<T, unknown>>()
75+
76+
get row() {
77+
return this.context.row
78+
}
79+
}

Diff for: ‎examples/angular/expanding/src/app/makeData.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { faker } from '@faker-js/faker'
2+
3+
export type Person = {
4+
firstName: string
5+
lastName: string
6+
age: number
7+
visits: number
8+
progress: number
9+
status: 'relationship' | 'complicated' | 'single'
10+
subRows?: Person[]
11+
}
12+
13+
const range = (len: number) => {
14+
const arr: number[] = []
15+
for (let i = 0; i < len; i++) {
16+
arr.push(i)
17+
}
18+
return arr
19+
}
20+
21+
const newPerson = (): Person => {
22+
return {
23+
firstName: faker.person.firstName(),
24+
lastName: faker.person.lastName(),
25+
age: faker.number.int(40),
26+
visits: faker.number.int(1000),
27+
progress: faker.number.int(100),
28+
status: faker.helpers.shuffle<Person['status']>([
29+
'relationship',
30+
'complicated',
31+
'single',
32+
])[0]!,
33+
}
34+
}
35+
36+
export function makeData(...lens: number[]) {
37+
const makeDataLevel = (depth = 0): Person[] => {
38+
const len = lens[depth]!
39+
return range(len).map((d): Person => {
40+
return {
41+
...newPerson(),
42+
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
43+
}
44+
})
45+
}
46+
47+
return makeDataLevel()
48+
}

Diff for: ‎examples/angular/expanding/src/assets/.gitkeep

Whitespace-only changes.

Diff for: ‎examples/angular/expanding/src/favicon.ico

14.7 KB
Binary file not shown.

Diff for: ‎examples/angular/expanding/src/index.html

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Expanding</title>
6+
<base href="/" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1" />
8+
<link rel="icon" type="image/x-icon" href="favicon.ico" />
9+
<script type="module" src="https://cdn.skypack.dev/twind/shim"></script>
10+
</head>
11+
<body>
12+
<app-root></app-root>
13+
</body>
14+
</html>

Diff for: ‎examples/angular/expanding/src/main.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { bootstrapApplication } from '@angular/platform-browser'
2+
import { appConfig } from './app/app.config'
3+
import { AppComponent } from './app/app.component'
4+
5+
bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err))

Diff for: ‎examples/angular/expanding/src/styles.scss

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
html {
2+
font-family: sans-serif;
3+
font-size: 14px;
4+
}
5+
6+
table {
7+
border: 1px solid lightgray;
8+
}
9+
10+
tbody {
11+
border-bottom: 1px solid lightgray;
12+
}
13+
14+
th {
15+
border-bottom: 1px solid lightgray;
16+
border-right: 1px solid lightgray;
17+
padding: 2px 4px;
18+
}
19+
20+
tfoot {
21+
color: gray;
22+
}
23+
24+
tfoot th {
25+
font-weight: normal;
26+
}

Diff for: ‎examples/angular/expanding/tsconfig.app.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
2+
{
3+
"extends": "./tsconfig.json",
4+
"compilerOptions": {
5+
"outDir": "./out-tsc/app",
6+
"types": []
7+
},
8+
"files": ["src/main.ts"],
9+
"include": ["src/**/*.d.ts"]
10+
}

Diff for: ‎examples/angular/expanding/tsconfig.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
2+
{
3+
"compileOnSave": false,
4+
"compilerOptions": {
5+
"baseUrl": "src",
6+
"outDir": "./dist/out-tsc",
7+
"forceConsistentCasingInFileNames": true,
8+
"strict": true,
9+
"noImplicitOverride": true,
10+
"noPropertyAccessFromIndexSignature": true,
11+
"noImplicitReturns": true,
12+
"noFallthroughCasesInSwitch": true,
13+
"skipLibCheck": true,
14+
"esModuleInterop": true,
15+
"sourceMap": true,
16+
"declaration": false,
17+
"experimentalDecorators": true,
18+
"moduleResolution": "node",
19+
"importHelpers": true,
20+
"target": "ES2022",
21+
"module": "ES2022",
22+
"useDefineForClassFields": false,
23+
"lib": ["ES2022", "dom"]
24+
},
25+
"angularCompilerOptions": {
26+
"enableI18nLegacyMessageIdFormat": false,
27+
"strictInjectionParameters": true,
28+
"strictInputAccessModifiers": true,
29+
"strictTemplates": true
30+
}
31+
}

Diff for: ‎examples/angular/expanding/tsconfig.spec.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
2+
{
3+
"extends": "./tsconfig.json",
4+
"compilerOptions": {
5+
"outDir": "./out-tsc/spec",
6+
"types": ["jasmine"]
7+
},
8+
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "Node.js",
3+
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
4+
}

Diff for: ‎examples/angular/sub-components/.editorconfig

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Editor configuration, see https://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.ts]
12+
quote_type = single
13+
14+
[*.md]
15+
max_line_length = off
16+
trim_trailing_whitespace = false

Diff for: ‎examples/angular/sub-components/.gitignore

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# See http://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# Compiled output
4+
/dist
5+
/tmp
6+
/out-tsc
7+
/bazel-out
8+
9+
# Node
10+
/node_modules
11+
npm-debug.log
12+
yarn-error.log
13+
14+
# IDEs and editors
15+
.idea/
16+
.project
17+
.classpath
18+
.c9/
19+
*.launch
20+
.settings/
21+
*.sublime-workspace
22+
23+
# Visual Studio Code
24+
.vscode/*
25+
!.vscode/settings.json
26+
!.vscode/tasks.json
27+
!.vscode/launch.json
28+
!.vscode/extensions.json
29+
.history/*
30+
31+
# Miscellaneous
32+
/.angular/cache
33+
.sass-cache/
34+
/connect.lock
35+
/coverage
36+
/libpeerconnection.log
37+
testem.log
38+
/typings
39+
40+
# System files
41+
.DS_Store
42+
Thumbs.db

Diff for: ‎examples/angular/sub-components/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Basic
2+
3+
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2.
4+
5+
## Development server
6+
7+
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.
8+
9+
## Code scaffolding
10+
11+
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`.
12+
13+
## Build
14+
15+
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16+
17+
## Running unit tests
18+
19+
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20+
21+
## Running end-to-end tests
22+
23+
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.
24+
25+
## Further help
26+
27+
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.

Diff for: ‎examples/angular/sub-components/angular.json

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{
2+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3+
"version": 1,
4+
"newProjectRoot": "projects",
5+
"projects": {
6+
"sub-components": {
7+
"cli": {
8+
"cache": {
9+
"enabled": false
10+
}
11+
},
12+
"projectType": "application",
13+
"schematics": {
14+
"@schematics/angular:component": {
15+
"style": "scss"
16+
}
17+
},
18+
"root": "",
19+
"sourceRoot": "src",
20+
"prefix": "app",
21+
"architect": {
22+
"build": {
23+
"builder": "@angular-devkit/build-angular:application",
24+
"options": {
25+
"outputPath": "dist/sub-components",
26+
"index": "src/index.html",
27+
"browser": "src/main.ts",
28+
"polyfills": ["zone.js"],
29+
"tsConfig": "tsconfig.app.json",
30+
"inlineStyleLanguage": "scss",
31+
"assets": ["src/favicon.ico", "src/assets"],
32+
"styles": ["src/styles.scss"],
33+
"scripts": []
34+
},
35+
"configurations": {
36+
"production": {
37+
"budgets": [],
38+
"outputHashing": "all"
39+
},
40+
"development": {
41+
"optimization": false,
42+
"extractLicenses": false,
43+
"sourceMap": true
44+
}
45+
},
46+
"defaultConfiguration": "production"
47+
},
48+
"serve": {
49+
"builder": "@angular-devkit/build-angular:dev-server",
50+
"configurations": {
51+
"production": {
52+
"buildTarget": "sub-components:build:production"
53+
},
54+
"development": {
55+
"buildTarget": "sub-components:build:development"
56+
}
57+
},
58+
"defaultConfiguration": "development"
59+
},
60+
"extract-i18n": {
61+
"builder": "@angular-devkit/build-angular:extract-i18n",
62+
"options": {
63+
"buildTarget": "sub-components:build"
64+
}
65+
},
66+
"test": {
67+
"builder": "@angular-devkit/build-angular:karma",
68+
"options": {
69+
"polyfills": ["zone.js", "zone.js/testing"],
70+
"tsConfig": "tsconfig.spec.json",
71+
"inlineStyleLanguage": "scss",
72+
"assets": ["src/favicon.ico", "src/assets"],
73+
"styles": ["src/styles.scss"],
74+
"scripts": []
75+
}
76+
}
77+
}
78+
}
79+
},
80+
"cli": {
81+
"analytics": false
82+
}
83+
}

Diff for: ‎examples/angular/sub-components/package.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "tanstack-table-example-angular-sub-components",
3+
"version": "0.0.0",
4+
"scripts": {
5+
"ng": "ng",
6+
"start": "ng serve",
7+
"build": "ng build",
8+
"watch": "ng build --watch --configuration development",
9+
"test": "ng test"
10+
},
11+
"private": true,
12+
"dependencies": {
13+
"@angular/animations": "^17.3.9",
14+
"@angular/common": "^17.3.9",
15+
"@angular/compiler": "^17.3.9",
16+
"@angular/core": "^17.3.9",
17+
"@angular/forms": "^17.3.9",
18+
"@angular/platform-browser": "^17.3.9",
19+
"@angular/platform-browser-dynamic": "^17.3.9",
20+
"@angular/router": "^17.3.9",
21+
"@faker-js/faker": "^8.4.1",
22+
"@tanstack/angular-table": "^8.21.0",
23+
"rxjs": "~7.8.1",
24+
"zone.js": "~0.14.4"
25+
},
26+
"devDependencies": {
27+
"@angular-devkit/build-angular": "^17.3.8",
28+
"@angular/cli": "^17.3.8",
29+
"@angular/compiler-cli": "^17.3.9",
30+
"tslib": "^2.6.2",
31+
"typescript": "5.4.5"
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<div class="p-2">
2+
<div class="overflow-x-auto">
3+
<table>
4+
<thead>
5+
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
6+
<tr>
7+
@for (header of headerGroup.headers; track header.id) {
8+
<th [colSpan]="header.colSpan">
9+
@if (!header.isPlaceholder) {
10+
<ng-container
11+
*flexRender="
12+
header.column.columnDef.header;
13+
props: header.getContext();
14+
let header
15+
"
16+
>
17+
<div [innerHTML]="header"></div>
18+
</ng-container>
19+
}
20+
</th>
21+
}
22+
</tr>
23+
}
24+
</thead>
25+
<tbody>
26+
@for (row of table.getRowModel().rows; track row.id) {
27+
<!-- first row is a normal row -->
28+
<tr>
29+
@for (cell of row.getVisibleCells(); track cell.id) {
30+
<td>
31+
<ng-container
32+
*flexRender="
33+
cell.column.columnDef.cell;
34+
props: cell.getContext();
35+
let cell
36+
"
37+
>
38+
<div [innerHTML]="cell"></div>
39+
</ng-container>
40+
</td>
41+
}
42+
</tr>
43+
44+
@if (row.getIsExpanded()) {
45+
<tr>
46+
<!-- 2nd row is a custom 1 cell row -->
47+
<td [colSpan]="row.getVisibleCells().length">
48+
<ng-container
49+
*ngTemplateOutlet="
50+
subComponentTemplate;
51+
context: { $implicit: row }
52+
"
53+
/>
54+
</td>
55+
</tr>
56+
}
57+
}
58+
</tbody>
59+
</table>
60+
</div>
61+
</div>
62+
63+
<ng-template #subComponentTemplate let-row>
64+
<pre [style.font-size.px]="10" class="bg-gray-100">
65+
<code>
66+
{{ row.original | json }}
67+
</code>
68+
</pre>
69+
</ng-template>
+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
computed,
5+
signal,
6+
} from '@angular/core'
7+
import {
8+
ColumnDef,
9+
createAngularTable,
10+
ExpandedState,
11+
flexRenderComponent,
12+
FlexRenderDirective,
13+
getCoreRowModel,
14+
getExpandedRowModel,
15+
getFilteredRowModel,
16+
getPaginationRowModel,
17+
} from '@tanstack/angular-table'
18+
import { makeData, type Person } from './makeData'
19+
import { ReactiveFormsModule } from '@angular/forms'
20+
import { ExpandableCell, ExpanderCell } from './expandable-cell'
21+
import { JsonPipe, NgTemplateOutlet } from '@angular/common'
22+
23+
const columns: ColumnDef<Person>[] = [
24+
{
25+
header: 'Name',
26+
footer: props => props.column.id,
27+
columns: [
28+
{
29+
id: 'expander',
30+
header: () => null,
31+
cell: ({ row }) => {
32+
if (!row.getCanExpand()) {
33+
return '🔵'
34+
}
35+
return flexRenderComponent(ExpanderCell, {
36+
inputs: {
37+
expanded: row.getIsExpanded(),
38+
},
39+
outputs: {
40+
click: row.getToggleExpandedHandler(),
41+
},
42+
})
43+
},
44+
},
45+
{
46+
accessorKey: 'firstName',
47+
header: 'First Name',
48+
cell: () => flexRenderComponent(ExpandableCell),
49+
footer: props => props.column.id,
50+
},
51+
{
52+
accessorFn: row => row.lastName,
53+
id: 'lastName',
54+
cell: info => info.getValue(),
55+
header: () => 'Last Name',
56+
footer: props => props.column.id,
57+
},
58+
],
59+
},
60+
{
61+
header: 'Info',
62+
footer: props => props.column.id,
63+
columns: [
64+
{
65+
accessorKey: 'age',
66+
header: () => 'Age',
67+
footer: props => props.column.id,
68+
},
69+
{
70+
header: 'More Info',
71+
columns: [
72+
{
73+
accessorKey: 'visits',
74+
header: () => 'Visits',
75+
footer: props => props.column.id,
76+
},
77+
{
78+
accessorKey: 'status',
79+
header: 'Status',
80+
footer: props => props.column.id,
81+
},
82+
{
83+
accessorKey: 'progress',
84+
header: 'Profile Progress',
85+
footer: props => props.column.id,
86+
},
87+
],
88+
},
89+
],
90+
},
91+
]
92+
93+
@Component({
94+
selector: 'app-root',
95+
standalone: true,
96+
imports: [
97+
FlexRenderDirective,
98+
ReactiveFormsModule,
99+
JsonPipe,
100+
NgTemplateOutlet,
101+
],
102+
templateUrl: './app.component.html',
103+
changeDetection: ChangeDetectionStrategy.OnPush,
104+
})
105+
export class AppComponent {
106+
readonly data = signal<Person[]>(makeData(10))
107+
readonly expanded = signal<ExpandedState>({})
108+
109+
readonly table = createAngularTable(() => ({
110+
data: this.data(),
111+
columns,
112+
state: {
113+
expanded: this.expanded(),
114+
},
115+
onExpandedChange: updater =>
116+
typeof updater === 'function'
117+
? this.expanded.update(updater)
118+
: this.expanded.set(updater),
119+
getRowCanExpand: () => true,
120+
getCoreRowModel: getCoreRowModel(),
121+
getExpandedRowModel: getExpandedRowModel(),
122+
}))
123+
}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ApplicationConfig } from '@angular/core'
2+
3+
export const appConfig: ApplicationConfig = {
4+
providers: [],
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
input,
5+
output,
6+
} from '@angular/core'
7+
import {
8+
type HeaderContext,
9+
injectFlexRenderContext,
10+
type Table,
11+
CellContext,
12+
} from '@tanstack/angular-table'
13+
14+
@Component({
15+
standalone: true,
16+
template: `
17+
<button (click)="click.emit($event)">
18+
{{ expanded() ? '👇' : '👉' }}
19+
</button>
20+
`,
21+
changeDetection: ChangeDetectionStrategy.OnPush,
22+
})
23+
export class ExpanderCell<T> {
24+
readonly expanded = input.required<boolean>()
25+
26+
readonly click = output<MouseEvent>()
27+
}
28+
29+
@Component({
30+
standalone: true,
31+
template: `
32+
<div [style.--depth]="row.depth">
33+
{{ context.getValue() }}
34+
</div>
35+
`,
36+
changeDetection: ChangeDetectionStrategy.OnPush,
37+
styles: `
38+
:host {
39+
> div {
40+
padding-left: calc(2rem * var(--depth, 1));
41+
}
42+
}
43+
`,
44+
})
45+
export class ExpandableCell<T> {
46+
readonly context = injectFlexRenderContext<CellContext<T, unknown>>()
47+
48+
get row() {
49+
return this.context.row
50+
}
51+
}

Diff for: ‎examples/angular/sub-components/src/app/makeData.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { faker } from '@faker-js/faker'
2+
3+
export type Person = {
4+
firstName: string
5+
lastName: string
6+
age: number
7+
visits: number
8+
progress: number
9+
status: 'relationship' | 'complicated' | 'single'
10+
subRows?: Person[]
11+
}
12+
13+
const range = (len: number) => {
14+
const arr: number[] = []
15+
for (let i = 0; i < len; i++) {
16+
arr.push(i)
17+
}
18+
return arr
19+
}
20+
21+
const newPerson = (): Person => {
22+
return {
23+
firstName: faker.person.firstName(),
24+
lastName: faker.person.lastName(),
25+
age: faker.number.int(40),
26+
visits: faker.number.int(1000),
27+
progress: faker.number.int(100),
28+
status: faker.helpers.shuffle<Person['status']>([
29+
'relationship',
30+
'complicated',
31+
'single',
32+
])[0]!,
33+
}
34+
}
35+
36+
export function makeData(...lens: number[]) {
37+
const makeDataLevel = (depth = 0): Person[] => {
38+
const len = lens[depth]!
39+
return range(len).map((d): Person => {
40+
return {
41+
...newPerson(),
42+
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
43+
}
44+
})
45+
}
46+
47+
return makeDataLevel()
48+
}

Diff for: ‎examples/angular/sub-components/src/assets/.gitkeep

Whitespace-only changes.

Diff for: ‎examples/angular/sub-components/src/favicon.ico

14.7 KB
Binary file not shown.

Diff for: ‎examples/angular/sub-components/src/index.html

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Expanding</title>
6+
<base href="/" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1" />
8+
<link rel="icon" type="image/x-icon" href="favicon.ico" />
9+
<script type="module" src="https://cdn.skypack.dev/twind/shim"></script>
10+
</head>
11+
<body>
12+
<app-root></app-root>
13+
</body>
14+
</html>

Diff for: ‎examples/angular/sub-components/src/main.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { bootstrapApplication } from '@angular/platform-browser'
2+
import { appConfig } from './app/app.config'
3+
import { AppComponent } from './app/app.component'
4+
5+
bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err))

Diff for: ‎examples/angular/sub-components/src/styles.scss

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
html {
2+
font-family: sans-serif;
3+
font-size: 14px;
4+
}
5+
6+
table {
7+
border: 1px solid lightgray;
8+
}
9+
10+
tbody {
11+
border-bottom: 1px solid lightgray;
12+
}
13+
14+
th {
15+
border-bottom: 1px solid lightgray;
16+
border-right: 1px solid lightgray;
17+
padding: 2px 4px;
18+
}
19+
20+
tfoot {
21+
color: gray;
22+
}
23+
24+
tfoot th {
25+
font-weight: normal;
26+
}

Diff for: ‎examples/angular/sub-components/tsconfig.app.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
2+
{
3+
"extends": "./tsconfig.json",
4+
"compilerOptions": {
5+
"outDir": "./out-tsc/app",
6+
"types": []
7+
},
8+
"files": ["src/main.ts"],
9+
"include": ["src/**/*.d.ts"]
10+
}

Diff for: ‎examples/angular/sub-components/tsconfig.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
2+
{
3+
"compileOnSave": false,
4+
"compilerOptions": {
5+
"baseUrl": "src",
6+
"outDir": "./dist/out-tsc",
7+
"forceConsistentCasingInFileNames": true,
8+
"strict": true,
9+
"noImplicitOverride": true,
10+
"noPropertyAccessFromIndexSignature": true,
11+
"noImplicitReturns": true,
12+
"noFallthroughCasesInSwitch": true,
13+
"skipLibCheck": true,
14+
"esModuleInterop": true,
15+
"sourceMap": true,
16+
"declaration": false,
17+
"experimentalDecorators": true,
18+
"moduleResolution": "node",
19+
"importHelpers": true,
20+
"target": "ES2022",
21+
"module": "ES2022",
22+
"useDefineForClassFields": false,
23+
"lib": ["ES2022", "dom"]
24+
},
25+
"angularCompilerOptions": {
26+
"enableI18nLegacyMessageIdFormat": false,
27+
"strictInjectionParameters": true,
28+
"strictInputAccessModifiers": true,
29+
"strictTemplates": true
30+
}
31+
}

Diff for: ‎examples/angular/sub-components/tsconfig.spec.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
2+
{
3+
"extends": "./tsconfig.json",
4+
"compilerOptions": {
5+
"outDir": "./out-tsc/spec",
6+
"types": ["jasmine"]
7+
},
8+
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
9+
}

Diff for: ‎pnpm-lock.yaml

+111-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.