Skip to content

Commit 30c57f4

Browse files
committed
refactor composable table examples with products/users table components, add missing header/table components
1 parent 173db1d commit 30c57f4

10 files changed

Lines changed: 476 additions & 149 deletions

File tree

Lines changed: 10 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,13 @@
1-
<div class="p-2">
2-
<div class="table-container" [tanStackTable]="table">
3-
<ng-container
4-
*ngComponentOutlet="
5-
table.TableToolbar;
6-
inputs: { title: 'Users Table', onRefresh }
7-
"
8-
/>
1+
<h1>Composable Tables Example</h1>
2+
<p class="description">
3+
Both tables below use the same <code>useAppTable</code> hook and
4+
shareable components, but with different data types and column
5+
configurations.
6+
</p>
97

10-
<table>
11-
<thead>
12-
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
13-
<tr>
14-
@for (header of headerGroup.headers; track header.id) {
15-
@if (!header.isPlaceholder) {
16-
<th [tanStackTableHeader]="header">
17-
<ng-container *flexRenderHeader="header; let header">
18-
{{ header }}
19-
</ng-container>
20-
</th>
21-
}
22-
}
23-
</tr>
24-
}
25-
</thead>
26-
<tbody>
27-
@for (row of table.getRowModel().rows; track row.id) {
28-
<tr>
29-
@for (cell of row.getAllCells(); track cell.id) {
30-
<td [tanStackTableCell]="cell">
31-
<ng-container *flexRenderCell="cell; let cell">
32-
{{ cell }}
33-
</ng-container>
34-
</td>
35-
}
36-
</tr>
37-
}
38-
</tbody>
8+
<users-table/>
399

40-
<tfoot>
41-
@for (footerGroup of table.getFooterGroups(); track footerGroup.id) {
42-
<tr>
43-
@for (footer of footerGroup.headers; track footer.id) {
44-
<th [colSpan]="footer.colSpan" [tanStackTableHeader]="footer">
45-
@if (!footer.isPlaceholder) {
46-
<ng-container *flexRenderFooter="footer; let footer">
47-
{{ footer }}
48-
</ng-container>
49-
}
50-
</th>
51-
}
52-
</tr>
53-
}
54-
</tfoot>
55-
</table>
56-
</div>
10+
<div class="table-divider"></div>
11+
12+
<products-table/>
5713

58-
<div class="h-4"></div>
59-
</div>
Lines changed: 8 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,14 @@
1-
import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
2-
import {
3-
FlexRender,
4-
TanStackTable,
5-
TanStackTableCell,
6-
TanStackTableHeader,
7-
flexRenderComponent,
8-
} from '@tanstack/angular-table'
9-
import { NgComponentOutlet } from '@angular/common'
10-
import { createAppColumnHelper, injectAppTable } from './table'
11-
import { makeData } from './makeData'
12-
import type { Person } from './makeData'
13-
14-
// Create column helpers with TFeatures already bound - only need TData!
15-
const personColumnHelper = createAppColumnHelper<Person>()
1+
import { ChangeDetectionStrategy, Component } from '@angular/core'
2+
import { UsersTable } from './components/users-table/users-table'
3+
import { ProductsTable } from './components/products-table/products-table'
164

175
@Component({
186
selector: 'app-root',
19-
imports: [
20-
FlexRender,
21-
TanStackTableHeader,
22-
TanStackTableCell,
23-
NgComponentOutlet,
24-
TanStackTable,
25-
],
7+
imports: [UsersTable, ProductsTable],
8+
host: {
9+
class: 'app',
10+
},
2611
templateUrl: './app.component.html',
2712
changeDetection: ChangeDetectionStrategy.OnPush,
2813
})
29-
export class AppComponent {
30-
readonly data = signal(makeData(5000))
31-
32-
readonly columns = personColumnHelper.columns([
33-
personColumnHelper.accessor('firstName', {
34-
header: 'First Name',
35-
footer: ({ header }) => flexRenderComponent(header.FooterColumnId),
36-
cell: ({ cell }) => flexRenderComponent(cell.TextCell),
37-
}),
38-
personColumnHelper.accessor('lastName', {
39-
header: 'Last Name',
40-
footer: ({ header }) => flexRenderComponent(header.FooterColumnId),
41-
cell: ({ cell }) => flexRenderComponent(cell.TextCell),
42-
}),
43-
personColumnHelper.accessor('age', {
44-
header: 'Age',
45-
footer: ({ header }) => flexRenderComponent(header.FooterSum),
46-
cell: ({ cell }) => flexRenderComponent(cell.NumberCell),
47-
}),
48-
personColumnHelper.accessor('visits', {
49-
header: 'Visits',
50-
footer: ({ header }) => flexRenderComponent(header.FooterSum),
51-
cell: ({ cell }) => flexRenderComponent(cell.NumberCell),
52-
}),
53-
personColumnHelper.accessor('status', {
54-
header: 'Status',
55-
footer: ({ header }) => flexRenderComponent(header.FooterColumnId),
56-
cell: ({ cell }) => flexRenderComponent(cell.StatusCell),
57-
}),
58-
personColumnHelper.accessor('progress', {
59-
header: 'Progress',
60-
footer: ({ header }) => flexRenderComponent(header.FooterSum),
61-
cell: ({ cell }) => flexRenderComponent(cell.ProgressCell),
62-
}),
63-
personColumnHelper.display({
64-
id: 'actions',
65-
header: 'Actions',
66-
cell: ({ cell }) => flexRenderComponent(cell.RowActionsCell),
67-
}),
68-
])
69-
70-
table = injectAppTable(() => ({
71-
columns: this.columns,
72-
data: this.data(),
73-
debugTable: true,
74-
// more table options
75-
}))
76-
77-
onRefresh = () => {
78-
this.data.set([...makeData(5000)])
79-
}
80-
}
14+
export class AppComponent {}

examples/angular/composable-tables/src/app/components/header-components.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,44 @@
1010
// }
1111

1212
import { Component, computed } from '@angular/core'
13+
import { flexRenderComponent } from '@tanstack/angular-table'
14+
import { FormsModule } from '@angular/forms'
1315
import { injectTableHeaderContext } from '../table'
16+
import type { FlexRenderComponent } from '@tanstack/angular-table'
1417

15-
// @Component({
16-
// selector: 'app-sort-indicator',
17-
// host: {
18-
// class: 'sort-indicator',
19-
// },
20-
// template: ` {{ sorted === 'asc' ? '🔼' : '🔽' }} `,
21-
// })
22-
// export class SortIndicator {
23-
// readonly context = injectTableHeaderContext()
24-
// }
18+
export function SortIndicator(): string | null {
19+
const header = injectTableHeaderContext()
20+
const sorted = header().column.getIsSorted()
21+
if (!sorted) {
22+
return null
23+
}
24+
return `<span class="sort-indicator">${sorted === 'asc' ? '🔼' : '🔽'}</span>`
25+
}
26+
27+
export function ColumnFilter(): FlexRenderComponent | null {
28+
const header = injectTableHeaderContext()
29+
if (!header().column.getCanFilter()) return null
30+
return flexRenderComponent(_ColumnFilter)
31+
}
32+
33+
@Component({
34+
template: `
35+
<div class="column-filter" (click)="$event.stopPropagation()">
36+
<input
37+
type="text"
38+
[ngModel]="filterValue()"
39+
(ngModelChange)="header().column.setFilterValue($event)"
40+
[placeholder]="placeholder()"
41+
/>
42+
</div>
43+
`,
44+
imports: [FormsModule],
45+
})
46+
export class _ColumnFilter {
47+
readonly header = injectTableHeaderContext()
48+
readonly filterValue = computed(() => this.header().column.getFilterValue())
49+
readonly placeholder = computed(() => `Filter ${this.header().column.id}...`)
50+
}
2551

2652
@Component({
2753
selector: 'span',
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<div class="table-container" [tanStackTable]="table">
2+
<ng-container
3+
*ngComponentOutlet="
4+
table.TableToolbar;
5+
inputs: { title: 'Products Table', onRefresh }
6+
"
7+
/>
8+
9+
<table>
10+
<thead>
11+
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
12+
<tr>
13+
@for (_header of headerGroup.headers; track _header.id) {
14+
@let header = table.appHeader(_header);
15+
@if (!header.isPlaceholder) {
16+
<th
17+
[tanStackTableHeader]="header"
18+
(click)="header.column.toggleSorting()"
19+
>
20+
<ng-container *flexRenderHeader="header; let header">
21+
{{ header }}
22+
</ng-container>
23+
24+
<ng-container
25+
*flexRender="
26+
header.SortIndicator;
27+
props: header.getContext();
28+
let value
29+
"
30+
>
31+
<div [innerHTML]="value"></div>
32+
</ng-container>
33+
34+
<ng-container
35+
*flexRender="
36+
header.ColumnFilter;
37+
props: header.getContext();
38+
let value
39+
"
40+
>
41+
<div [innerHTML]="value"></div>
42+
</ng-container>
43+
</th>
44+
}
45+
}
46+
</tr>
47+
}
48+
</thead>
49+
<tbody>
50+
@for (row of table.getRowModel().rows; track row.id) {
51+
<tr>
52+
@for (cell of row.getAllCells(); track cell.id) {
53+
<td [tanStackTableCell]="cell">
54+
<ng-container *flexRenderCell="cell; let cell">
55+
{{ cell }}
56+
</ng-container>
57+
</td>
58+
}
59+
</tr>
60+
}
61+
</tbody>
62+
63+
<tfoot>
64+
@for (footerGroup of table.getFooterGroups(); track footerGroup.id) {
65+
<tr>
66+
@for (footer of footerGroup.headers; track footer.id) {
67+
<th [colSpan]="footer.colSpan" [tanStackTableHeader]="footer">
68+
@if (!footer.isPlaceholder) {
69+
<ng-container *flexRenderFooter="footer; let footer">
70+
{{ footer }}
71+
</ng-container>
72+
}
73+
</th>
74+
}
75+
</tr>
76+
}
77+
</tfoot>
78+
</table>
79+
80+
<ng-container *ngComponentOutlet="table.PaginationControls"/>
81+
82+
<ng-container *ngComponentOutlet="table.RowCount"/>
83+
</div>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
2+
import { NgComponentOutlet } from '@angular/common'
3+
import {
4+
FlexRender,
5+
TanStackTable,
6+
TanStackTableCell,
7+
TanStackTableHeader,
8+
} from '@tanstack/angular-table'
9+
import { makeProductData } from '../../makeData'
10+
import { createAppColumnHelper, injectAppTable } from '../../table'
11+
import type { Product } from '../../makeData'
12+
13+
export const productColumnHelper = createAppColumnHelper<Product>()
14+
15+
@Component({
16+
selector: 'products-table',
17+
templateUrl: './products-table.html',
18+
imports: [
19+
NgComponentOutlet,
20+
FlexRender,
21+
TanStackTable,
22+
TanStackTableHeader,
23+
TanStackTableCell,
24+
],
25+
changeDetection: ChangeDetectionStrategy.OnPush,
26+
})
27+
export class ProductsTable {
28+
readonly data = signal(makeProductData(5000))
29+
30+
readonly columns = productColumnHelper.columns([
31+
productColumnHelper.accessor('name', {
32+
header: 'Product Name',
33+
footer: (props) => props.column.id,
34+
cell: ({ cell }) => cell.TextCell,
35+
}),
36+
productColumnHelper.accessor('category', {
37+
header: 'Category',
38+
footer: (props) => props.column.id,
39+
cell: ({ cell }) => cell.CategoryCell,
40+
}),
41+
productColumnHelper.accessor('price', {
42+
header: 'Price',
43+
footer: (props) => props.column.id,
44+
cell: ({ cell }) => cell.PriceCell,
45+
}),
46+
productColumnHelper.accessor('stock', {
47+
header: 'In Stock',
48+
footer: (props) => props.column.id,
49+
cell: ({ cell }) => cell.NumberCell,
50+
}),
51+
productColumnHelper.accessor('rating', {
52+
header: 'Rating',
53+
footer: (props) => props.column.id,
54+
cell: ({ cell }) => cell.ProgressCell,
55+
}),
56+
])
57+
58+
table = injectAppTable(() => ({
59+
columns: this.columns,
60+
data: this.data(),
61+
getRowId: (row) => row.id,
62+
// more table options
63+
}))
64+
65+
onRefresh = () => {
66+
this.data.set([...makeProductData(5000)])
67+
}
68+
}

0 commit comments

Comments
 (0)