From 86e7c51da07d3ed9181716cd5083e181c622277a Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Fri, 3 May 2024 21:19:49 +0200 Subject: [PATCH 01/12] feat(angular-table): support render dynamic components and templateRefs in table * update row selection example using dynamic rendered components --- .../row-selection/src/app/app.component.html | 73 ++++++-------- .../row-selection/src/app/app.component.ts | 96 +++++++++++++++++-- .../angular/row-selection/src/app/columns.ts | 68 ------------- .../src/app/selection-column.component.ts | 32 +++++++ packages/angular-table/src/flex-render.ts | 82 ++++++++++++---- packages/angular-table/src/index.ts | 2 +- 6 files changed, 215 insertions(+), 138 deletions(-) delete mode 100644 examples/angular/row-selection/src/app/columns.ts create mode 100644 examples/angular/row-selection/src/app/selection-column.component.ts diff --git a/examples/angular/row-selection/src/app/app.component.html b/examples/angular/row-selection/src/app/app.component.html index 6842ffd3f4..c2dfb725f7 100644 --- a/examples/angular/row-selection/src/app/app.component.html +++ b/examples/angular/row-selection/src/app/app.component.html @@ -8,32 +8,23 @@ @for (header of headerGroup.headers; track header.id) { @if (!header.isPlaceholder) { - @if (header.id == 'select') { - - } @else { - - {{ headerCell }} - + + {{ headerCell }} + - @if (header.column.getCanFilter()) { -
- -
- } + @if (header.column.getCanFilter()) { +
+ +
} } @@ -46,25 +37,15 @@ @for (cell of row.getVisibleCells(); track cell.id) { - @if (cell.id.endsWith('select')) { - - } @else { - - {{ renderCell }} - - } + + {{ renderCell }} + } @@ -165,3 +146,7 @@
{{ stringifiedRowSelection() }}
+ + + Age 🥳 + diff --git a/examples/angular/row-selection/src/app/app.component.ts b/examples/angular/row-selection/src/app/app.component.ts index a1b2c691fb..fb0285cdce 100644 --- a/examples/angular/row-selection/src/app/app.component.ts +++ b/examples/angular/row-selection/src/app/app.component.ts @@ -1,32 +1,114 @@ -import { Component, computed, signal } from '@angular/core' import { + ChangeDetectionStrategy, + Component, + computed, + signal, + TemplateRef, + viewChild, +} from '@angular/core' +import { + ColumnDef, createAngularTable, + FlexRenderComponent, FlexRenderDirective, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, RowSelectionState, } from '@tanstack/angular-table' -import { columns } from './columns' import { FilterComponent } from './filter' -import { makeData } from './makeData' +import { makeData, type Person } from './makeData' import { FormsModule } from '@angular/forms' +import { + TableHeadSelectionComponent, + TableRowSelectionComponent, +} from './selection-column.component' +import { toSignal } from '@angular/core/rxjs-interop' +import { interval } from 'rxjs' +import { JsonPipe } from '@angular/common' @Component({ selector: 'app-root', standalone: true, - imports: [FilterComponent, FlexRenderDirective, FormsModule], + imports: [FilterComponent, FlexRenderDirective, FormsModule, JsonPipe], templateUrl: './app.component.html', styleUrl: './app.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent { private readonly rowSelection = signal({}) readonly globalFilter = signal('') readonly data = signal(makeData(10_000)) + readonly ageHeaderCell = viewChild.required>( + 'ageHeaderCell' + ) + + readonly columns: ColumnDef[] = [ + { + id: 'select', + header: props => { + return new FlexRenderComponent(TableHeadSelectionComponent, { props }) + }, + cell: props => { + return new FlexRenderComponent(TableRowSelectionComponent, { props }) + }, + }, + { + header: 'Name', + footer: props => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: info => info.getValue(), + footer: props => props.column.id, + header: 'First name', + }, + { + accessorFn: row => row.lastName, + id: 'lastName', + cell: info => info.getValue(), + header: () => 'Last Name', + footer: props => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: props => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => this.ageHeaderCell(), + footer: props => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => 'Visits', + footer: props => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: props => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: props => props.column.id, + }, + ], + }, + ], + }, + ] + table = createAngularTable(() => ({ data: this.data(), - columns: columns, + columns: this.columns, state: { rowSelection: this.rowSelection(), }, @@ -45,10 +127,6 @@ export class AppComponent { debugTable: true, })) - constructor() { - console.log('table', this.table) - } - readonly stringifiedRowSelection = computed(() => JSON.stringify(this.rowSelection(), null, 2) ) diff --git a/examples/angular/row-selection/src/app/columns.ts b/examples/angular/row-selection/src/app/columns.ts deleted file mode 100644 index 9be530ef80..0000000000 --- a/examples/angular/row-selection/src/app/columns.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { ColumnDef } from '@tanstack/angular-table' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] -} -export const columns: ColumnDef[] = [ - { - id: 'select', - header: ({ table }) => table, - cell: ({ row }) => row, - }, - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - header: 'First Name', - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => 'Last Name', - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => 'Visits', - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] diff --git a/examples/angular/row-selection/src/app/selection-column.component.ts b/examples/angular/row-selection/src/app/selection-column.component.ts new file mode 100644 index 0000000000..77a646ac8d --- /dev/null +++ b/examples/angular/row-selection/src/app/selection-column.component.ts @@ -0,0 +1,32 @@ +import { type CellContext, type HeaderContext } from '@tanstack/angular-table' +import { Component, input } from '@angular/core' + +@Component({ + selector: 'app-table-head-selection', + template: ` + + `, + standalone: true, +}) +export class TableHeadSelectionComponent { + props = input.required>() +} + +@Component({ + template: ` + + `, + standalone: true, +}) +export class TableRowSelectionComponent { + props = input.required>() +} diff --git a/packages/angular-table/src/flex-render.ts b/packages/angular-table/src/flex-render.ts index de2a021df4..dbcc90f31a 100644 --- a/packages/angular-table/src/flex-render.ts +++ b/packages/angular-table/src/flex-render.ts @@ -1,21 +1,36 @@ import { Directive, + inject, + InjectionToken, + Injector, Input, type OnInit, TemplateRef, + type Type, ViewContainerRef, } from '@angular/core' +type FlexRenderContent> = + | string + | FlexRenderComponent + | TemplateRef<{ $implicit: TProps }> + @Directive({ selector: '[flexRender]', standalone: true, }) -export class FlexRenderDirective implements OnInit { - @Input({ required: true }) - flexRender!: any | ((props: any) => any) +export class FlexRenderDirective> + implements OnInit +{ + @Input({ required: true, alias: 'flexRender' }) + content: string | ((props: TProps) => FlexRenderContent) | undefined = + undefined + + @Input({ required: true, alias: 'flexRenderProps' }) + props: TProps = {} as TProps - @Input({ required: true }) - flexRenderProps!: any + @Input({ required: false, alias: 'flexRenderInjector' }) + injector: Injector = inject(Injector) constructor( private viewContainerRef: ViewContainerRef, @@ -23,28 +38,63 @@ export class FlexRenderDirective implements OnInit { ) {} ngOnInit(): void { - this.renderComponent() + this.render() } - renderComponent() { + render() { this.viewContainerRef.clear() - if (!this.flexRender) { + const { content, props } = this + if (!this.content) { return null } - if (typeof this.flexRender === 'string') { - const getContext = () => this.flexRender - this.viewContainerRef.createEmbeddedView(this.templateRef, { + + if (typeof content === 'string') { + return this.viewContainerRef.createEmbeddedView(this.templateRef, { + get $implicit() { + return content + }, + }) + } + if (typeof content === 'function') { + return this.renderContent(content(props)); + } + } + + private renderContent(content: FlexRenderContent) { + if (typeof content === 'string') { + return this.viewContainerRef.createEmbeddedView(this.templateRef, { get $implicit() { - return getContext() + return content }, }) - } else if (typeof this.flexRender === 'function') { - const getContext = () => this.flexRender(this.flexRenderProps) - this.viewContainerRef.createEmbeddedView(this.templateRef, { + } + if (content instanceof TemplateRef) { + const props = () => this.props + return this.viewContainerRef.createEmbeddedView(content, { get $implicit() { - return getContext() + return props() }, }) } + return this.renderComponent(content) + } + + private renderComponent(flexRenderComponent: FlexRenderComponent) { + const { component, props } = flexRenderComponent + const componentRef = this.viewContainerRef.createComponent(component, { + injector: this.injector, + }) + for (const prop in props) { + if (componentRef.instance?.hasOwnProperty(prop)) { + componentRef.setInput(prop, props[prop]) + } + } } } + +export class FlexRenderComponent> { + constructor( + readonly component: Type, + readonly props: T + ) {} +} diff --git a/packages/angular-table/src/index.ts b/packages/angular-table/src/index.ts index f2d6734a9c..4bfca00bdc 100644 --- a/packages/angular-table/src/index.ts +++ b/packages/angular-table/src/index.ts @@ -19,7 +19,7 @@ import { lazyInit } from './lazy-signal-initializer' export * from '@tanstack/table-core' -export { FlexRenderDirective } from './flex-render' +export { FlexRenderDirective, FlexRenderComponent } from './flex-render' export function createAngularTable( options: () => TableOptions From 9f7a5101c5a660a1fd5cb1cf010317c58853f21f Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Fri, 3 May 2024 22:04:04 +0200 Subject: [PATCH 02/12] support change detection on push with flex render --- .../row-selection/src/app/app.component.ts | 18 +++---- .../src/app/selection-column.component.ts | 31 +++++++---- packages/angular-table/src/flex-render.ts | 51 ++++++++++++++++--- packages/angular-table/src/index.ts | 8 ++- 4 files changed, 77 insertions(+), 31 deletions(-) diff --git a/examples/angular/row-selection/src/app/app.component.ts b/examples/angular/row-selection/src/app/app.component.ts index fb0285cdce..5fcd2cd71d 100644 --- a/examples/angular/row-selection/src/app/app.component.ts +++ b/examples/angular/row-selection/src/app/app.component.ts @@ -23,14 +23,11 @@ import { TableHeadSelectionComponent, TableRowSelectionComponent, } from './selection-column.component' -import { toSignal } from '@angular/core/rxjs-interop' -import { interval } from 'rxjs' -import { JsonPipe } from '@angular/common' @Component({ selector: 'app-root', standalone: true, - imports: [FilterComponent, FlexRenderDirective, FormsModule, JsonPipe], + imports: [FilterComponent, FlexRenderDirective, FormsModule], templateUrl: './app.component.html', styleUrl: './app.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -40,18 +37,17 @@ export class AppComponent { readonly globalFilter = signal('') readonly data = signal(makeData(10_000)) - readonly ageHeaderCell = viewChild.required>( - 'ageHeaderCell' - ) + readonly ageHeaderCell = + viewChild.required>('ageHeaderCell') readonly columns: ColumnDef[] = [ { id: 'select', - header: props => { - return new FlexRenderComponent(TableHeadSelectionComponent, { props }) + header: () => { + return new FlexRenderComponent(TableHeadSelectionComponent) }, - cell: props => { - return new FlexRenderComponent(TableRowSelectionComponent, { props }) + cell: () => { + return new FlexRenderComponent(TableRowSelectionComponent) }, }, { diff --git a/examples/angular/row-selection/src/app/selection-column.component.ts b/examples/angular/row-selection/src/app/selection-column.component.ts index 77a646ac8d..b4f3e1c008 100644 --- a/examples/angular/row-selection/src/app/selection-column.component.ts +++ b/examples/angular/row-selection/src/app/selection-column.component.ts @@ -1,32 +1,43 @@ -import { type CellContext, type HeaderContext } from '@tanstack/angular-table' -import { Component, input } from '@angular/core' +import { + type CellContext, + type HeaderContext, + injectFlexRenderContext, +} from '@tanstack/angular-table' +import { ChangeDetectionStrategy, Component } from '@angular/core' @Component({ - selector: 'app-table-head-selection', template: ` `, + host: { + class: 'px-1 block', + }, standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TableHeadSelectionComponent { - props = input.required>() + context = injectFlexRenderContext>() } @Component({ template: ` `, + host: { + class: 'px-1 block', + }, standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TableRowSelectionComponent { - props = input.required>() + context = injectFlexRenderContext>() } diff --git a/packages/angular-table/src/flex-render.ts b/packages/angular-table/src/flex-render.ts index dbcc90f31a..a502cd6c99 100644 --- a/packages/angular-table/src/flex-render.ts +++ b/packages/angular-table/src/flex-render.ts @@ -1,5 +1,9 @@ import { + ChangeDetectorRef, + ComponentRef, Directive, + type DoCheck, + EmbeddedViewRef, inject, InjectionToken, Injector, @@ -20,7 +24,7 @@ type FlexRenderContent> = standalone: true, }) export class FlexRenderDirective> - implements OnInit + implements OnInit, DoCheck { @Input({ required: true, alias: 'flexRender' }) content: string | ((props: TProps) => FlexRenderContent) | undefined = @@ -37,8 +41,16 @@ export class FlexRenderDirective> private templateRef: TemplateRef ) {} + ref?: ComponentRef | EmbeddedViewRef | null = null + ngOnInit(): void { - this.render() + this.ref = this.render() + } + + ngDoCheck() { + if (this.ref instanceof ComponentRef) { + this.ref.injector.get(ChangeDetectorRef).markForCheck() + } } render() { @@ -56,8 +68,9 @@ export class FlexRenderDirective> }) } if (typeof content === 'function') { - return this.renderContent(content(props)); + return this.renderContent(content(props)) } + return null } private renderContent(content: FlexRenderContent) { @@ -80,21 +93,43 @@ export class FlexRenderDirective> } private renderComponent(flexRenderComponent: FlexRenderComponent) { - const { component, props } = flexRenderComponent + const { component, inputs, injector } = flexRenderComponent + + const getContext = () => this.props + + const proxy = new Proxy(this.props, { + get: (_, key) => getContext()?.[key as keyof typeof _], + }) + + const componentInjector = Injector.create({ + parent: injector ?? this.injector, + providers: [{ provide: FlexRenderComponentProps, useValue: proxy }], + }) + const componentRef = this.viewContainerRef.createComponent(component, { - injector: this.injector, + injector: componentInjector, }) - for (const prop in props) { + for (const prop in inputs) { if (componentRef.instance?.hasOwnProperty(prop)) { - componentRef.setInput(prop, props[prop]) + componentRef.setInput(prop, inputs[prop]) } } + return componentRef } } export class FlexRenderComponent> { constructor( readonly component: Type, - readonly props: T + readonly inputs: T = {} as T, + readonly injector?: Injector ) {} } + +const FlexRenderComponentProps = new InjectionToken>( + '[@tanstack/angular-table] Flex render component context props' +) + +export function injectFlexRenderContext>(): T { + return inject(FlexRenderComponentProps) +} diff --git a/packages/angular-table/src/index.ts b/packages/angular-table/src/index.ts index 4bfca00bdc..6449629a0c 100644 --- a/packages/angular-table/src/index.ts +++ b/packages/angular-table/src/index.ts @@ -19,7 +19,11 @@ import { lazyInit } from './lazy-signal-initializer' export * from '@tanstack/table-core' -export { FlexRenderDirective, FlexRenderComponent } from './flex-render' +export { + FlexRenderDirective, + FlexRenderComponent, + injectFlexRenderContext, +} from './flex-render' export function createAngularTable( options: () => TableOptions @@ -85,7 +89,7 @@ export function createAngularTable( } ) - return proxifyTable(tableValue) + return tableValue() }) ) } From d6252e8f0d39e9ee80042404f714ba920d170a84 Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Fri, 3 May 2024 22:41:04 +0200 Subject: [PATCH 03/12] add column ordering example --- .../angular/column-ordering/.editorconfig | 16 +++ examples/angular/column-ordering/.gitignore | 42 +++++++ .../column-ordering/.vscode/extensions.json | 4 + .../column-ordering/.vscode/launch.json | 20 +++ .../column-ordering/.vscode/tasks.json | 42 +++++++ examples/angular/column-ordering/README.md | 27 ++++ examples/angular/column-ordering/angular.json | 94 ++++++++++++++ examples/angular/column-ordering/package.json | 38 ++++++ .../src/app/app.component.html | 102 +++++++++++++++ .../column-ordering/src/app/app.component.ts | 118 ++++++++++++++++++ .../column-ordering/src/app/app.config.ts | 5 + .../column-ordering/src/app/makeData.ts | 48 +++++++ .../column-ordering/src/assets/.gitkeep | 0 .../angular/column-ordering/src/favicon.ico | Bin 0 -> 15086 bytes .../angular/column-ordering/src/index.html | 14 +++ examples/angular/column-ordering/src/main.ts | 5 + .../angular/column-ordering/src/styles.scss | 35 ++++++ .../angular/column-ordering/tsconfig.app.json | 10 ++ .../angular/column-ordering/tsconfig.json | 31 +++++ .../column-ordering/tsconfig.spec.json | 9 ++ pnpm-lock.yaml | 74 ++++++++++- 21 files changed, 732 insertions(+), 2 deletions(-) create mode 100644 examples/angular/column-ordering/.editorconfig create mode 100644 examples/angular/column-ordering/.gitignore create mode 100644 examples/angular/column-ordering/.vscode/extensions.json create mode 100644 examples/angular/column-ordering/.vscode/launch.json create mode 100644 examples/angular/column-ordering/.vscode/tasks.json create mode 100644 examples/angular/column-ordering/README.md create mode 100644 examples/angular/column-ordering/angular.json create mode 100644 examples/angular/column-ordering/package.json create mode 100644 examples/angular/column-ordering/src/app/app.component.html create mode 100644 examples/angular/column-ordering/src/app/app.component.ts create mode 100644 examples/angular/column-ordering/src/app/app.config.ts create mode 100644 examples/angular/column-ordering/src/app/makeData.ts create mode 100644 examples/angular/column-ordering/src/assets/.gitkeep create mode 100644 examples/angular/column-ordering/src/favicon.ico create mode 100644 examples/angular/column-ordering/src/index.html create mode 100644 examples/angular/column-ordering/src/main.ts create mode 100644 examples/angular/column-ordering/src/styles.scss create mode 100644 examples/angular/column-ordering/tsconfig.app.json create mode 100644 examples/angular/column-ordering/tsconfig.json create mode 100644 examples/angular/column-ordering/tsconfig.spec.json diff --git a/examples/angular/column-ordering/.editorconfig b/examples/angular/column-ordering/.editorconfig new file mode 100644 index 0000000000..59d9a3a3e7 --- /dev/null +++ b/examples/angular/column-ordering/.editorconfig @@ -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 diff --git a/examples/angular/column-ordering/.gitignore b/examples/angular/column-ordering/.gitignore new file mode 100644 index 0000000000..0711527ef9 --- /dev/null +++ b/examples/angular/column-ordering/.gitignore @@ -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 diff --git a/examples/angular/column-ordering/.vscode/extensions.json b/examples/angular/column-ordering/.vscode/extensions.json new file mode 100644 index 0000000000..77b374577d --- /dev/null +++ b/examples/angular/column-ordering/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/examples/angular/column-ordering/.vscode/launch.json b/examples/angular/column-ordering/.vscode/launch.json new file mode 100644 index 0000000000..925af83705 --- /dev/null +++ b/examples/angular/column-ordering/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/examples/angular/column-ordering/.vscode/tasks.json b/examples/angular/column-ordering/.vscode/tasks.json new file mode 100644 index 0000000000..a298b5bd87 --- /dev/null +++ b/examples/angular/column-ordering/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/examples/angular/column-ordering/README.md b/examples/angular/column-ordering/README.md new file mode 100644 index 0000000000..5da97a87d1 --- /dev/null +++ b/examples/angular/column-ordering/README.md @@ -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. diff --git a/examples/angular/column-ordering/angular.json b/examples/angular/column-ordering/angular.json new file mode 100644 index 0000000000..2c71bfb7c0 --- /dev/null +++ b/examples/angular/column-ordering/angular.json @@ -0,0 +1,94 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "basic": { + "cli": { + "cache": { + "enabled": false + } + }, + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/basic", + "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-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "basic:build:production" + }, + "development": { + "buildTarget": "basic:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "basic:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.scss"], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": "73f296b8-52f2-4044-acca-9178df581487" + } +} diff --git a/examples/angular/column-ordering/package.json b/examples/angular/column-ordering/package.json new file mode 100644 index 0000000000..2e0c5944cb --- /dev/null +++ b/examples/angular/column-ordering/package.json @@ -0,0 +1,38 @@ +{ + "name": "tanstack-table-example-angular-column-ordering", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^17.3.1", + "@angular/common": "^17.3.1", + "@angular/compiler": "^17.3.1", + "@angular/core": "^17.3.1", + "@angular/forms": "^17.3.1", + "@angular/platform-browser": "^17.3.1", + "@angular/platform-browser-dynamic": "^17.3.1", + "@tanstack/angular-table": "^8.14.0", + "rxjs": "~7.8.1", + "zone.js": "~0.14.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^17.3.1", + "@angular/cli": "^17.3.1", + "@angular/compiler-cli": "^17.3.1", + "@types/jasmine": "~5.1.4", + "jasmine-core": "~5.1.2", + "karma": "~6.4.3", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.1", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "tslib": "^2.6.2", + "typescript": "5.4.5" + } +} diff --git a/examples/angular/column-ordering/src/app/app.component.html b/examples/angular/column-ordering/src/app/app.component.html new file mode 100644 index 0000000000..fca57e650a --- /dev/null +++ b/examples/angular/column-ordering/src/app/app.component.html @@ -0,0 +1,102 @@ +
+
+
+ +
+ + @for (column of table.getAllLeafColumns(); track column.id) { +
+ +
+ } +
+ +
+
+ + +
+ + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (header of footerGroup.headers; track header.id) { + + } + + } + +
+ @if (!header.isPlaceholder) { + + {{ header }} + + } +
+ + {{ cell }} + +
+ @if (!header.isPlaceholder) { + + {{ header }} + + } +
+ +
+
{{ stringifiedColumnOrdering() }}
+
diff --git a/examples/angular/column-ordering/src/app/app.component.ts b/examples/angular/column-ordering/src/app/app.component.ts new file mode 100644 index 0000000000..2f447c6f61 --- /dev/null +++ b/examples/angular/column-ordering/src/app/app.component.ts @@ -0,0 +1,118 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + ColumnDef, + type ColumnOrderState, + createAngularTable, + FlexRenderDirective, + getCoreRowModel, + type VisibilityState, +} from '@tanstack/angular-table' +import { makeData, type Person } from './makeData' +import { faker } from '@faker-js/faker' + +const defaultColumns: ColumnDef[] = [ + { + header: 'Name', + footer: props => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: info => info.getValue(), + footer: props => props.column.id, + }, + { + accessorFn: row => row.lastName, + id: 'lastName', + cell: info => info.getValue(), + header: () => 'Last Name', + footer: props => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: props => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: props => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => 'Visits', + footer: props => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: props => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: props => props.column.id, + }, + ], + }, + ], + }, +] + +@Component({ + selector: 'app-root', + standalone: true, + imports: [FlexRenderDirective], + templateUrl: './app.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppComponent { + readonly data = signal(makeData(20)) + readonly columnVisibility = signal({}) + readonly columnOrder = signal([]) + + readonly table = createAngularTable(() => ({ + data: this.data(), + columns: defaultColumns, + state: { + columnOrder: this.columnOrder(), + columnVisibility: this.columnVisibility(), + }, + getCoreRowModel: getCoreRowModel(), + onColumnVisibilityChange: updaterOrValue => { + typeof updaterOrValue === 'function' + ? this.columnVisibility.update(updaterOrValue) + : this.columnVisibility.set(updaterOrValue) + }, + onColumnOrderChange: updaterOrValue => { + typeof updaterOrValue === 'function' + ? this.columnOrder.update(updaterOrValue) + : this.columnOrder.set(updaterOrValue) + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + })) + + readonly stringifiedColumnOrdering = computed(() => { + return JSON.stringify(this.table.getState().columnOrder) + }) + + randomizeColumns() { + this.table.setColumnOrder( + faker.helpers.shuffle(this.table.getAllLeafColumns().map(d => d.id)) + ) + } + + rerender() { + this.data.set([...makeData(20)]) + } +} diff --git a/examples/angular/column-ordering/src/app/app.config.ts b/examples/angular/column-ordering/src/app/app.config.ts new file mode 100644 index 0000000000..f27099f33c --- /dev/null +++ b/examples/angular/column-ordering/src/app/app.config.ts @@ -0,0 +1,5 @@ +import { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [], +} diff --git a/examples/angular/column-ordering/src/app/makeData.ts b/examples/angular/column-ordering/src/app/makeData.ts new file mode 100644 index 0000000000..331dd1eb19 --- /dev/null +++ b/examples/angular/column-ordering/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Person[] +} + +const range = (len: number) => { + const arr: number[] = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0]!, + } +} + +export function makeData(...lens: number[]) { + const makeDataLevel = (depth = 0): Person[] => { + const len = lens[depth]! + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/column-ordering/src/assets/.gitkeep b/examples/angular/column-ordering/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/angular/column-ordering/src/favicon.ico b/examples/angular/column-ordering/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 zcmd^G33O9Omi+`8$@{|M-I6TH3wzF-p5CV8o}7f~KxR60LK+ApEFB<$bcciv%@SmA zV{n>g85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~ zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@ z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l* zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4 zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp| z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9 znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3 zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=? zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO zKO?E0 zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4 z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG` z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8 z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3 z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$ zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~ z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~ zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@ z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8 zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{ z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL` zt?XK-iTEOWV}f>Pz3H-s*>W z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/examples/angular/column-ordering/src/index.html b/examples/angular/column-ordering/src/index.html new file mode 100644 index 0000000000..a4bb987648 --- /dev/null +++ b/examples/angular/column-ordering/src/index.html @@ -0,0 +1,14 @@ + + + + + Basic + + + + + + + + + diff --git a/examples/angular/column-ordering/src/main.ts b/examples/angular/column-ordering/src/main.ts new file mode 100644 index 0000000000..0c3b92057c --- /dev/null +++ b/examples/angular/column-ordering/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { AppComponent } from './app/app.component' + +bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) diff --git a/examples/angular/column-ordering/src/styles.scss b/examples/angular/column-ordering/src/styles.scss new file mode 100644 index 0000000000..93034cdd1b --- /dev/null +++ b/examples/angular/column-ordering/src/styles.scss @@ -0,0 +1,35 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} diff --git a/examples/angular/column-ordering/tsconfig.app.json b/examples/angular/column-ordering/tsconfig.app.json new file mode 100644 index 0000000000..84f1f992d2 --- /dev/null +++ b/examples/angular/column-ordering/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/examples/angular/column-ordering/tsconfig.json b/examples/angular/column-ordering/tsconfig.json new file mode 100644 index 0000000000..b58d3efc71 --- /dev/null +++ b/examples/angular/column-ordering/tsconfig.json @@ -0,0 +1,31 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "src", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/examples/angular/column-ordering/tsconfig.spec.json b/examples/angular/column-ordering/tsconfig.spec.json new file mode 100644 index 0000000000..47e3dd7551 --- /dev/null +++ b/examples/angular/column-ordering/tsconfig.spec.json @@ -0,0 +1,9 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["jasmine"] + }, + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98099223dd..00b65cc5c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,6 +178,76 @@ importers: specifier: 5.4.5 version: 5.4.5 + examples/angular/column-ordering: + dependencies: + '@angular/animations': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1) + '@angular/common': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1)(rxjs@7.8.1) + '@angular/compiler': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1) + '@angular/core': + specifier: ^17.3.1 + version: 17.3.1(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/forms': + specifier: ^17.3.1 + version: 17.3.6(@angular/common@17.3.6)(@angular/core@17.3.1)(@angular/platform-browser@17.3.6)(rxjs@7.8.1) + '@angular/platform-browser': + specifier: ^17.3.1 + version: 17.3.6(@angular/animations@17.3.6)(@angular/common@17.3.6)(@angular/core@17.3.1) + '@angular/platform-browser-dynamic': + specifier: ^17.3.1 + version: 17.3.6(@angular/common@17.3.6)(@angular/compiler@17.3.6)(@angular/core@17.3.1)(@angular/platform-browser@17.3.6) + '@tanstack/angular-table': + specifier: ^8.14.0 + version: link:../../../packages/angular-table + rxjs: + specifier: ~7.8.1 + version: 7.8.1 + zone.js: + specifier: ~0.14.4 + version: 0.14.4 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^17.3.1 + version: 17.3.6(@angular/compiler-cli@17.3.6)(@types/node@20.12.7)(karma@6.4.3)(ng-packagr@17.3.0)(typescript@5.4.5) + '@angular/cli': + specifier: ^17.3.1 + version: 17.3.6 + '@angular/compiler-cli': + specifier: ^17.3.1 + version: 17.3.6(@angular/compiler@17.3.6)(typescript@5.4.5) + '@types/jasmine': + specifier: ~5.1.4 + version: 5.1.4 + jasmine-core: + specifier: ~5.1.2 + version: 5.1.2 + karma: + specifier: ~6.4.3 + version: 6.4.3 + karma-chrome-launcher: + specifier: ~3.2.0 + version: 3.2.0 + karma-coverage: + specifier: ~2.2.1 + version: 2.2.1 + karma-jasmine: + specifier: ~5.1.0 + version: 5.1.0(karma@6.4.3) + karma-jasmine-html-reporter: + specifier: ~2.1.0 + version: 2.1.0(jasmine-core@5.1.2)(karma-jasmine@5.1.0)(karma@6.4.3) + tslib: + specifier: ^2.6.2 + version: 2.6.2 + typescript: + specifier: 5.4.5 + version: 5.4.5 + examples/angular/column-visibility: dependencies: '@angular/animations': @@ -3916,7 +3986,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.0 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.0) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.0) '@babel/helper-plugin-utils': 7.24.0 dev: true @@ -14039,7 +14109,7 @@ packages: peerDependencies: solid-js: ^1.3 dependencies: - '@babel/generator': 7.24.1 + '@babel/generator': 7.24.4 '@babel/helper-module-imports': 7.24.3 '@babel/types': 7.24.0 solid-js: 1.8.17 From 3a095299105e049fbf3b8934cf51a9d5ed944e8a Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Fri, 3 May 2024 23:24:40 +0200 Subject: [PATCH 04/12] fix flexRender change detection issues --- .../person-table/person-table.component.ts | 8 +--- packages/angular-table/src/flex-render.ts | 46 ++++++++++++------- packages/angular-table/src/index.ts | 2 +- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/examples/angular/signal-input/src/app/person-table/person-table.component.ts b/examples/angular/signal-input/src/app/person-table/person-table.component.ts index daad82c9ac..299e02de06 100644 --- a/examples/angular/signal-input/src/app/person-table/person-table.component.ts +++ b/examples/angular/signal-input/src/app/person-table/person-table.component.ts @@ -19,6 +19,7 @@ import { getPaginationRowModel, PaginationState, } from '@tanstack/angular-table' + @Component({ selector: 'app-person-table', templateUrl: 'person-table.component.html', @@ -46,6 +47,7 @@ export class PersonTableComponent { ] table = createAngularTable(() => { + const data = this.data(); return { data: this.data(), columns: this.columns, @@ -74,10 +76,4 @@ export class PersonTableComponent { onPageSizeChange(event: any) { this.table.setPageSize(Number(event.target.value)) } - - constructor() { - setTimeout(() => { - console.log({ ...this.table }) - }, 1000) - } } diff --git a/packages/angular-table/src/flex-render.ts b/packages/angular-table/src/flex-render.ts index a502cd6c99..e5d710a065 100644 --- a/packages/angular-table/src/flex-render.ts +++ b/packages/angular-table/src/flex-render.ts @@ -50,6 +50,8 @@ export class FlexRenderDirective> ngDoCheck() { if (this.ref instanceof ComponentRef) { this.ref.injector.get(ChangeDetectorRef).markForCheck() + } else if (this.ref instanceof EmbeddedViewRef) { + this.ref.markForCheck() } } @@ -61,11 +63,7 @@ export class FlexRenderDirective> } if (typeof content === 'string') { - return this.viewContainerRef.createEmbeddedView(this.templateRef, { - get $implicit() { - return content - }, - }) + return this.renderStringContent() } if (typeof content === 'function') { return this.renderContent(content(props)) @@ -75,23 +73,30 @@ export class FlexRenderDirective> private renderContent(content: FlexRenderContent) { if (typeof content === 'string') { - return this.viewContainerRef.createEmbeddedView(this.templateRef, { - get $implicit() { - return content - }, - }) + return this.renderStringContent() } if (content instanceof TemplateRef) { - const props = () => this.props - return this.viewContainerRef.createEmbeddedView(content, { - get $implicit() { - return props() - }, - }) + return this.viewContainerRef.createEmbeddedView( + content, + this.getTemplateRefContext() + ) } return this.renderComponent(content) } + private renderStringContent() { + const context = () => { + return typeof this.content === 'string' + ? this.content + : this.content?.(this.props) + } + return this.viewContainerRef.createEmbeddedView(this.templateRef, { + get $implicit() { + return context() + }, + }) + } + private renderComponent(flexRenderComponent: FlexRenderComponent) { const { component, inputs, injector } = flexRenderComponent @@ -116,6 +121,15 @@ export class FlexRenderDirective> } return componentRef } + + private getTemplateRefContext() { + const getContext = () => this.props + return { + get $implicit() { + return getContext() + }, + } + } } export class FlexRenderComponent> { diff --git a/packages/angular-table/src/index.ts b/packages/angular-table/src/index.ts index 6449629a0c..ffbc501e08 100644 --- a/packages/angular-table/src/index.ts +++ b/packages/angular-table/src/index.ts @@ -89,7 +89,7 @@ export function createAngularTable( } ) - return tableValue() + return proxifyTable(tableValue) }) ) } From 7e9631b9043da5f57625bdbd0948885aa3a39e0a Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Sat, 4 May 2024 00:09:31 +0200 Subject: [PATCH 05/12] rename properties --- packages/angular-table/src/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/angular-table/src/index.ts b/packages/angular-table/src/index.ts index ffbc501e08..710720af46 100644 --- a/packages/angular-table/src/index.ts +++ b/packages/angular-table/src/index.ts @@ -48,8 +48,8 @@ export function createAngularTable( const state = signal(table.initialState) function updateOptions() { - const tableState = state() - const resolvedOptions = resolvedOptionsSignal() + const tableState = untracked(state) + const resolvedOptions = untracked(resolvedOptionsSignal) untracked(() => { table.setOptions(prev => ({ ...prev, @@ -67,11 +67,11 @@ export function createAngularTable( updateOptions() - let skip = true + let firstRender = true effect(() => { void [state(), resolvedOptionsSignal()] - if (skip) { - return (skip = false) + if (firstRender) { + return (firstRender = false) } untracked(() => { updateOptions() From cfbd43fe348a332aabacc7f3d404ee1a8b689a7a Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Sat, 4 May 2024 00:12:09 +0200 Subject: [PATCH 06/12] fix prettier and adjust example budget options --- examples/angular/column-ordering/angular.json | 15 ++------------- examples/angular/column-visibility/angular.json | 15 ++------------- examples/angular/signal-input/angular.json | 2 +- .../app/person-table/person-table.component.ts | 2 +- 4 files changed, 6 insertions(+), 28 deletions(-) diff --git a/examples/angular/column-ordering/angular.json b/examples/angular/column-ordering/angular.json index 2c71bfb7c0..add3c87198 100644 --- a/examples/angular/column-ordering/angular.json +++ b/examples/angular/column-ordering/angular.json @@ -22,7 +22,7 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/basic", + "outputPath": "dist/column-ordering", "index": "src/index.html", "browser": "src/main.ts", "polyfills": ["zone.js"], @@ -34,18 +34,7 @@ }, "configurations": { "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" - } - ], + "budgets": [], "outputHashing": "all" }, "development": { diff --git a/examples/angular/column-visibility/angular.json b/examples/angular/column-visibility/angular.json index 2c71bfb7c0..42b0d75bb7 100644 --- a/examples/angular/column-visibility/angular.json +++ b/examples/angular/column-visibility/angular.json @@ -22,7 +22,7 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/basic", + "outputPath": "dist/column-visibility", "index": "src/index.html", "browser": "src/main.ts", "polyfills": ["zone.js"], @@ -34,18 +34,7 @@ }, "configurations": { "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" - } - ], + "budgets": [], "outputHashing": "all" }, "development": { diff --git a/examples/angular/signal-input/angular.json b/examples/angular/signal-input/angular.json index 4a48e1faf9..1ce36507d6 100644 --- a/examples/angular/signal-input/angular.json +++ b/examples/angular/signal-input/angular.json @@ -17,7 +17,7 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/grouping", + "outputPath": "dist/signal-input", "index": "src/index.html", "browser": "src/main.ts", "polyfills": ["zone.js"], diff --git a/examples/angular/signal-input/src/app/person-table/person-table.component.ts b/examples/angular/signal-input/src/app/person-table/person-table.component.ts index 299e02de06..c0f88550e3 100644 --- a/examples/angular/signal-input/src/app/person-table/person-table.component.ts +++ b/examples/angular/signal-input/src/app/person-table/person-table.component.ts @@ -47,7 +47,7 @@ export class PersonTableComponent { ] table = createAngularTable(() => { - const data = this.data(); + const data = this.data() return { data: this.data(), columns: this.columns, From c01200bd45111f0dd94aa4c055a2b4d7a825b07e Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Sat, 4 May 2024 12:11:42 +0200 Subject: [PATCH 07/12] update basic example --- examples/angular/basic/src/app/app.component.html | 4 ++-- examples/angular/basic/src/app/app.component.ts | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/examples/angular/basic/src/app/app.component.html b/examples/angular/basic/src/app/app.component.html index cef2752dc6..68c81953a6 100644 --- a/examples/angular/basic/src/app/app.component.html +++ b/examples/angular/basic/src/app/app.component.html @@ -13,7 +13,7 @@ let header " > - {{ header }} +
} @@ -33,7 +33,7 @@ let cell " > - {{ cell }} +
} diff --git a/examples/angular/basic/src/app/app.component.ts b/examples/angular/basic/src/app/app.component.ts index 6ef9e63a95..540509e36b 100644 --- a/examples/angular/basic/src/app/app.component.ts +++ b/examples/angular/basic/src/app/app.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, - type OnInit, + Injectable, signal, } from '@angular/core' import { RouterOutlet } from '@angular/router' @@ -91,8 +91,8 @@ const defaultColumns: ColumnDef[] = [ styleUrl: './app.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class AppComponent implements OnInit { - data = signal([]) +export class AppComponent { + data = signal(defaultData) table = createAngularTable(() => ({ data: this.data(), @@ -101,11 +101,7 @@ export class AppComponent implements OnInit { debugTable: true, })) - ngOnInit() { - this.data.set(defaultData) - } - rerender() { - this.data.set(defaultData) + this.data.set([...defaultData.sort(() => -1)]) } } From c2f6e712d0c76d6440a554f7bd7c3a8d1fbd07e5 Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Sat, 4 May 2024 13:28:51 +0200 Subject: [PATCH 08/12] add again support for table signal --- packages/angular-table/src/index.ts | 7 ++++--- .../angular-table/src/lazy-signal-initializer.ts | 13 +++++++++++-- packages/angular-table/src/proxy.ts | 14 ++++++++++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/angular-table/src/index.ts b/packages/angular-table/src/index.ts index 710720af46..20e2db99b3 100644 --- a/packages/angular-table/src/index.ts +++ b/packages/angular-table/src/index.ts @@ -4,6 +4,7 @@ import { inject, Injector, runInInjectionContext, + type Signal, signal, untracked, } from '@angular/core' @@ -27,7 +28,7 @@ export { export function createAngularTable( options: () => TableOptions -): Table { +): Table & Signal> { const injector = inject(Injector) return lazyInit(() => @@ -79,7 +80,7 @@ export function createAngularTable( }) }) - const tableValue = computed( + const tableSignal = computed( () => { notifier() return table @@ -89,7 +90,7 @@ export function createAngularTable( } ) - return proxifyTable(tableValue) + return proxifyTable(tableSignal) }) ) } diff --git a/packages/angular-table/src/lazy-signal-initializer.ts b/packages/angular-table/src/lazy-signal-initializer.ts index 576d745a3d..af7636b0c3 100644 --- a/packages/angular-table/src/lazy-signal-initializer.ts +++ b/packages/angular-table/src/lazy-signal-initializer.ts @@ -1,4 +1,4 @@ -import { untracked } from '@angular/core' +import {untracked} from '@angular/core' /** * Implementation from @tanstack/angular-query @@ -15,7 +15,16 @@ export function lazyInit(initializer: () => T): T { queueMicrotask(() => initializeObject()) - return new Proxy({} as T, { + function table() {} + + return new Proxy(table as T, { + apply(target: T, thisArg: any, argArray: any[]): any { + initializeObject() + if (typeof object === 'function') { + return Reflect.apply(object, thisArg, argArray) + } + return Reflect.apply(target as any, thisArg, argArray) + }, get(_, prop, receiver) { initializeObject() return Reflect.get(object as T, prop, receiver) diff --git a/packages/angular-table/src/proxy.ts b/packages/angular-table/src/proxy.ts index e39873e49c..93de38aa7d 100644 --- a/packages/angular-table/src/proxy.ts +++ b/packages/angular-table/src/proxy.ts @@ -1,11 +1,18 @@ import { computed, type Signal, untracked } from '@angular/core' import { type Table } from '@tanstack/table-core' -export function proxifyTable(tableSignal: Signal>): Table { - const internalState = {} as Table +type TableSignal = Table & Signal> + +export function proxifyTable( + tableSignal: Signal> +): Table & Signal> { + const internalState = tableSignal as TableSignal return new Proxy(internalState, { - get(target: Table, property: keyof Table): any { + apply() { + return tableSignal() + }, + get(target, property: keyof Table): any { if (target[property]) { return target[property] } @@ -28,7 +35,6 @@ export function proxifyTable(tableSignal: Signal>): Table { return target[property] } } - // @ts-expect-error return (target[property] = table[property]) }, From e27356408a5c111c5d510b7e5acb4737cdabc281 Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Sat, 4 May 2024 15:23:36 +0200 Subject: [PATCH 09/12] add column-pinning example --- examples/angular/column-pinning/.editorconfig | 16 ++ examples/angular/column-pinning/.gitignore | 42 +++ .../column-pinning/.vscode/extensions.json | 4 + .../column-pinning/.vscode/launch.json | 20 ++ .../angular/column-pinning/.vscode/tasks.json | 42 +++ examples/angular/column-pinning/README.md | 27 ++ examples/angular/column-pinning/angular.json | 83 ++++++ examples/angular/column-pinning/package.json | 38 +++ .../column-pinning/src/app/app.component.html | 261 ++++++++++++++++++ .../column-pinning/src/app/app.component.ts | 137 +++++++++ .../column-pinning/src/app/app.config.ts | 5 + .../column-pinning/src/app/makeData.ts | 48 ++++ .../column-pinning/src/assets/.gitkeep | 0 .../angular/column-pinning/src/favicon.ico | Bin 0 -> 15086 bytes .../angular/column-pinning/src/index.html | 14 + examples/angular/column-pinning/src/main.ts | 5 + .../angular/column-pinning/src/styles.scss | 35 +++ .../angular/column-pinning/tsconfig.app.json | 10 + examples/angular/column-pinning/tsconfig.json | 31 +++ .../angular/column-pinning/tsconfig.spec.json | 9 + .../src/lazy-signal-initializer.ts | 2 +- pnpm-lock.yaml | 70 +++++ 22 files changed, 898 insertions(+), 1 deletion(-) create mode 100644 examples/angular/column-pinning/.editorconfig create mode 100644 examples/angular/column-pinning/.gitignore create mode 100644 examples/angular/column-pinning/.vscode/extensions.json create mode 100644 examples/angular/column-pinning/.vscode/launch.json create mode 100644 examples/angular/column-pinning/.vscode/tasks.json create mode 100644 examples/angular/column-pinning/README.md create mode 100644 examples/angular/column-pinning/angular.json create mode 100644 examples/angular/column-pinning/package.json create mode 100644 examples/angular/column-pinning/src/app/app.component.html create mode 100644 examples/angular/column-pinning/src/app/app.component.ts create mode 100644 examples/angular/column-pinning/src/app/app.config.ts create mode 100644 examples/angular/column-pinning/src/app/makeData.ts create mode 100644 examples/angular/column-pinning/src/assets/.gitkeep create mode 100644 examples/angular/column-pinning/src/favicon.ico create mode 100644 examples/angular/column-pinning/src/index.html create mode 100644 examples/angular/column-pinning/src/main.ts create mode 100644 examples/angular/column-pinning/src/styles.scss create mode 100644 examples/angular/column-pinning/tsconfig.app.json create mode 100644 examples/angular/column-pinning/tsconfig.json create mode 100644 examples/angular/column-pinning/tsconfig.spec.json diff --git a/examples/angular/column-pinning/.editorconfig b/examples/angular/column-pinning/.editorconfig new file mode 100644 index 0000000000..59d9a3a3e7 --- /dev/null +++ b/examples/angular/column-pinning/.editorconfig @@ -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 diff --git a/examples/angular/column-pinning/.gitignore b/examples/angular/column-pinning/.gitignore new file mode 100644 index 0000000000..0711527ef9 --- /dev/null +++ b/examples/angular/column-pinning/.gitignore @@ -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 diff --git a/examples/angular/column-pinning/.vscode/extensions.json b/examples/angular/column-pinning/.vscode/extensions.json new file mode 100644 index 0000000000..77b374577d --- /dev/null +++ b/examples/angular/column-pinning/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/examples/angular/column-pinning/.vscode/launch.json b/examples/angular/column-pinning/.vscode/launch.json new file mode 100644 index 0000000000..925af83705 --- /dev/null +++ b/examples/angular/column-pinning/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/examples/angular/column-pinning/.vscode/tasks.json b/examples/angular/column-pinning/.vscode/tasks.json new file mode 100644 index 0000000000..a298b5bd87 --- /dev/null +++ b/examples/angular/column-pinning/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/examples/angular/column-pinning/README.md b/examples/angular/column-pinning/README.md new file mode 100644 index 0000000000..5da97a87d1 --- /dev/null +++ b/examples/angular/column-pinning/README.md @@ -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. diff --git a/examples/angular/column-pinning/angular.json b/examples/angular/column-pinning/angular.json new file mode 100644 index 0000000000..7eaee1b793 --- /dev/null +++ b/examples/angular/column-pinning/angular.json @@ -0,0 +1,83 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "basic": { + "cli": { + "cache": { + "enabled": false + } + }, + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/column-pinning", + "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": [], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "basic:build:production" + }, + "development": { + "buildTarget": "basic:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "basic:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.scss"], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": "73f296b8-52f2-4044-acca-9178df581487" + } +} diff --git a/examples/angular/column-pinning/package.json b/examples/angular/column-pinning/package.json new file mode 100644 index 0000000000..7ddb6dc241 --- /dev/null +++ b/examples/angular/column-pinning/package.json @@ -0,0 +1,38 @@ +{ + "name": "tanstack-table-example-angular-column-pinning", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^17.3.1", + "@angular/common": "^17.3.1", + "@angular/compiler": "^17.3.1", + "@angular/core": "^17.3.1", + "@angular/forms": "^17.3.1", + "@angular/platform-browser": "^17.3.1", + "@angular/platform-browser-dynamic": "^17.3.1", + "@tanstack/angular-table": "^8.14.0", + "rxjs": "~7.8.1", + "zone.js": "~0.14.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^17.3.1", + "@angular/cli": "^17.3.1", + "@angular/compiler-cli": "^17.3.1", + "@types/jasmine": "~5.1.4", + "jasmine-core": "~5.1.2", + "karma": "~6.4.3", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.1", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "tslib": "^2.6.2", + "typescript": "5.4.5" + } +} diff --git a/examples/angular/column-pinning/src/app/app.component.html b/examples/angular/column-pinning/src/app/app.component.html new file mode 100644 index 0000000000..e002af2b70 --- /dev/null +++ b/examples/angular/column-pinning/src/app/app.component.html @@ -0,0 +1,261 @@ +
+
+
+ +
+ + @for (column of table.getAllLeafColumns(); track column.id) { +
+ +
+ } +
+ +
+ +
+ + +
+
+
+ +
+ +
+ + @if (split()) { + + + @for ( + headerGroup of table.getLeftHeaderGroups(); + track headerGroup.id + ) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + + @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { + + @for (cell of row.getLeftVisibleCells(); track cell.id) { + + } + + } + +
+
+ @if (!header.isPlaceholder) { + + {{ headerValue }} + + } +
+ + +
+ + {{ cellValue }} + +
+ } + + + + + @if ( + split() ? table.getCenterHeaderGroups() : table.getHeaderGroups(); + as headerGroups + ) { + @for (headerGroup of headerGroups; track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + } + + + @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { + @if ( + split() ? row.getCenterVisibleCells() : row.getVisibleCells(); + as cells + ) { + + @for (cell of cells; track cell.id) { + + } + + } + } + +
+
+ @if (!header.isPlaceholder) { + + {{ headerValue }} + + } + + +
+
+ + {{ cellValue }} + +
+ + + @if (split()) { + + + @for ( + headerGroup of table.getRightHeaderGroups(); + track headerGroup.id + ) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + + @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { + + @for (cell of row.getRightVisibleCells(); track cell.id) { + + } + + } + +
+
+ @if (!header.isPlaceholder) { + + {{ headerValue }} + + } +
+ + +
+ + {{ cellValue }} + +
+ } +
+ +
+
{{ stringifiedColumnPinning() }}
+
+ + + @if (!header.isPlaceholder && header.column.getCanPin()) { +
+ @if (header.column.getIsPinned() !== 'left') { + + } + + @if (header.column.getIsPinned()) { + + } + + @if (header.column.getIsPinned() !== 'right') { + + } +
+ } +
diff --git a/examples/angular/column-pinning/src/app/app.component.ts b/examples/angular/column-pinning/src/app/app.component.ts new file mode 100644 index 0000000000..3784a0782c --- /dev/null +++ b/examples/angular/column-pinning/src/app/app.component.ts @@ -0,0 +1,137 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + ColumnDef, + type ColumnOrderState, + type ColumnPinningState, + createAngularTable, + FlexRenderDirective, + getCoreRowModel, + type VisibilityState, +} from '@tanstack/angular-table' +import { makeData } from './makeData' +import { faker } from '@faker-js/faker' +import { NgTemplateOutlet, SlicePipe } from '@angular/common' + +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const defaultColumns: ColumnDef[] = [ + { + header: 'Name', + footer: props => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: info => info.getValue(), + footer: props => props.column.id, + }, + { + accessorFn: row => row.lastName, + id: 'lastName', + cell: info => info.getValue(), + header: () => 'Last Name', + footer: props => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: props => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: props => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => 'Visits', + footer: props => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: props => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: props => props.column.id, + }, + ], + }, + ], + }, +] + +@Component({ + selector: 'app-root', + standalone: true, + imports: [FlexRenderDirective, SlicePipe, NgTemplateOutlet], + templateUrl: './app.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppComponent { + readonly data = signal(makeData(5000)) + readonly columnVisibility = signal({}) + readonly columnOrder = signal([]) + readonly columnPinning = signal({}) + readonly split = signal(false) + + table = createAngularTable(() => ({ + data: this.data(), + columns: defaultColumns, + state: { + columnVisibility: this.columnVisibility(), + columnOrder: this.columnOrder(), + columnPinning: this.columnPinning(), + }, + onColumnVisibilityChange: updaterOrValue => { + typeof updaterOrValue === 'function' + ? this.columnVisibility.update(updaterOrValue) + : this.columnVisibility.set(updaterOrValue) + }, + onColumnOrderChange: updaterOrValue => { + typeof updaterOrValue === 'function' + ? this.columnOrder.update(updaterOrValue) + : this.columnOrder.set(updaterOrValue) + }, + onColumnPinningChange: updaterOrValue => { + typeof updaterOrValue === 'function' + ? this.columnPinning.update(updaterOrValue) + : this.columnPinning.set(updaterOrValue) + }, + getCoreRowModel: getCoreRowModel(), + debugTable: true, + debugHeaders: true, + debugColumns: true, + })) + + stringifiedColumnPinning = computed(() => { + return JSON.stringify(this.table.getState().columnPinning) + }) + + randomizeColumns() { + this.table.setColumnOrder( + faker.helpers.shuffle(this.table.getAllLeafColumns().map(d => d.id)) + ) + } + + rerender() { + this.data.set(makeData(5000)) + } +} diff --git a/examples/angular/column-pinning/src/app/app.config.ts b/examples/angular/column-pinning/src/app/app.config.ts new file mode 100644 index 0000000000..f27099f33c --- /dev/null +++ b/examples/angular/column-pinning/src/app/app.config.ts @@ -0,0 +1,5 @@ +import { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [], +} diff --git a/examples/angular/column-pinning/src/app/makeData.ts b/examples/angular/column-pinning/src/app/makeData.ts new file mode 100644 index 0000000000..331dd1eb19 --- /dev/null +++ b/examples/angular/column-pinning/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Person[] +} + +const range = (len: number) => { + const arr: number[] = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0]!, + } +} + +export function makeData(...lens: number[]) { + const makeDataLevel = (depth = 0): Person[] => { + const len = lens[depth]! + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/column-pinning/src/assets/.gitkeep b/examples/angular/column-pinning/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/angular/column-pinning/src/favicon.ico b/examples/angular/column-pinning/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 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 Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/examples/angular/column-pinning/src/index.html b/examples/angular/column-pinning/src/index.html new file mode 100644 index 0000000000..a4bb987648 --- /dev/null +++ b/examples/angular/column-pinning/src/index.html @@ -0,0 +1,14 @@ + + + + + Basic + + + + + + + + + diff --git a/examples/angular/column-pinning/src/main.ts b/examples/angular/column-pinning/src/main.ts new file mode 100644 index 0000000000..0c3b92057c --- /dev/null +++ b/examples/angular/column-pinning/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { AppComponent } from './app/app.component' + +bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) diff --git a/examples/angular/column-pinning/src/styles.scss b/examples/angular/column-pinning/src/styles.scss new file mode 100644 index 0000000000..93034cdd1b --- /dev/null +++ b/examples/angular/column-pinning/src/styles.scss @@ -0,0 +1,35 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} diff --git a/examples/angular/column-pinning/tsconfig.app.json b/examples/angular/column-pinning/tsconfig.app.json new file mode 100644 index 0000000000..84f1f992d2 --- /dev/null +++ b/examples/angular/column-pinning/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/examples/angular/column-pinning/tsconfig.json b/examples/angular/column-pinning/tsconfig.json new file mode 100644 index 0000000000..b58d3efc71 --- /dev/null +++ b/examples/angular/column-pinning/tsconfig.json @@ -0,0 +1,31 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "src", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/examples/angular/column-pinning/tsconfig.spec.json b/examples/angular/column-pinning/tsconfig.spec.json new file mode 100644 index 0000000000..47e3dd7551 --- /dev/null +++ b/examples/angular/column-pinning/tsconfig.spec.json @@ -0,0 +1,9 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["jasmine"] + }, + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/packages/angular-table/src/lazy-signal-initializer.ts b/packages/angular-table/src/lazy-signal-initializer.ts index af7636b0c3..65ca4037cd 100644 --- a/packages/angular-table/src/lazy-signal-initializer.ts +++ b/packages/angular-table/src/lazy-signal-initializer.ts @@ -1,4 +1,4 @@ -import {untracked} from '@angular/core' +import { untracked } from '@angular/core' /** * Implementation from @tanstack/angular-query diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00b65cc5c6..09c7ccdacd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -248,6 +248,76 @@ importers: specifier: 5.4.5 version: 5.4.5 + examples/angular/column-pinning: + dependencies: + '@angular/animations': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1) + '@angular/common': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1)(rxjs@7.8.1) + '@angular/compiler': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1) + '@angular/core': + specifier: ^17.3.1 + version: 17.3.1(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/forms': + specifier: ^17.3.1 + version: 17.3.6(@angular/common@17.3.6)(@angular/core@17.3.1)(@angular/platform-browser@17.3.6)(rxjs@7.8.1) + '@angular/platform-browser': + specifier: ^17.3.1 + version: 17.3.6(@angular/animations@17.3.6)(@angular/common@17.3.6)(@angular/core@17.3.1) + '@angular/platform-browser-dynamic': + specifier: ^17.3.1 + version: 17.3.6(@angular/common@17.3.6)(@angular/compiler@17.3.6)(@angular/core@17.3.1)(@angular/platform-browser@17.3.6) + '@tanstack/angular-table': + specifier: ^8.14.0 + version: link:../../../packages/angular-table + rxjs: + specifier: ~7.8.1 + version: 7.8.1 + zone.js: + specifier: ~0.14.4 + version: 0.14.4 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^17.3.1 + version: 17.3.6(@angular/compiler-cli@17.3.6)(@types/node@20.12.7)(karma@6.4.3)(ng-packagr@17.3.0)(typescript@5.4.5) + '@angular/cli': + specifier: ^17.3.1 + version: 17.3.6 + '@angular/compiler-cli': + specifier: ^17.3.1 + version: 17.3.6(@angular/compiler@17.3.6)(typescript@5.4.5) + '@types/jasmine': + specifier: ~5.1.4 + version: 5.1.4 + jasmine-core: + specifier: ~5.1.2 + version: 5.1.2 + karma: + specifier: ~6.4.3 + version: 6.4.3 + karma-chrome-launcher: + specifier: ~3.2.0 + version: 3.2.0 + karma-coverage: + specifier: ~2.2.1 + version: 2.2.1 + karma-jasmine: + specifier: ~5.1.0 + version: 5.1.0(karma@6.4.3) + karma-jasmine-html-reporter: + specifier: ~2.1.0 + version: 2.1.0(jasmine-core@5.1.2)(karma-jasmine@5.1.0)(karma@6.4.3) + tslib: + specifier: ^2.6.2 + version: 2.6.2 + typescript: + specifier: 5.4.5 + version: 5.4.5 + examples/angular/column-visibility: dependencies: '@angular/animations': From 3c164f324ec94edf4c0e1ee1f551e99882b96dfd Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Sat, 4 May 2024 15:43:15 +0200 Subject: [PATCH 10/12] add column pinning example --- .../column-pinning-sticky/.editorconfig | 16 ++ .../angular/column-pinning-sticky/.gitignore | 42 ++++++ .../.vscode/extensions.json | 4 + .../column-pinning-sticky/.vscode/launch.json | 20 +++ .../column-pinning-sticky/.vscode/tasks.json | 42 ++++++ .../angular/column-pinning-sticky/README.md | 27 ++++ .../column-pinning-sticky/angular.json | 83 +++++++++++ .../column-pinning-sticky/package.json | 38 +++++ .../src/app/app.component.html | 137 +++++++++++++++++ .../src/app/app.component.ts | 138 ++++++++++++++++++ .../src/app/app.config.ts | 5 + .../column-pinning-sticky/src/app/makeData.ts | 48 ++++++ .../column-pinning-sticky/src/assets/.gitkeep | 0 .../column-pinning-sticky/src/favicon.ico | Bin 0 -> 15086 bytes .../column-pinning-sticky/src/index.html | 14 ++ .../angular/column-pinning-sticky/src/main.ts | 5 + .../column-pinning-sticky/src/styles.scss | 50 +++++++ .../column-pinning-sticky/tsconfig.app.json | 10 ++ .../column-pinning-sticky/tsconfig.json | 31 ++++ .../column-pinning-sticky/tsconfig.spec.json | 9 ++ pnpm-lock.yaml | 70 +++++++++ 21 files changed, 789 insertions(+) create mode 100644 examples/angular/column-pinning-sticky/.editorconfig create mode 100644 examples/angular/column-pinning-sticky/.gitignore create mode 100644 examples/angular/column-pinning-sticky/.vscode/extensions.json create mode 100644 examples/angular/column-pinning-sticky/.vscode/launch.json create mode 100644 examples/angular/column-pinning-sticky/.vscode/tasks.json create mode 100644 examples/angular/column-pinning-sticky/README.md create mode 100644 examples/angular/column-pinning-sticky/angular.json create mode 100644 examples/angular/column-pinning-sticky/package.json create mode 100644 examples/angular/column-pinning-sticky/src/app/app.component.html create mode 100644 examples/angular/column-pinning-sticky/src/app/app.component.ts create mode 100644 examples/angular/column-pinning-sticky/src/app/app.config.ts create mode 100644 examples/angular/column-pinning-sticky/src/app/makeData.ts create mode 100644 examples/angular/column-pinning-sticky/src/assets/.gitkeep create mode 100644 examples/angular/column-pinning-sticky/src/favicon.ico create mode 100644 examples/angular/column-pinning-sticky/src/index.html create mode 100644 examples/angular/column-pinning-sticky/src/main.ts create mode 100644 examples/angular/column-pinning-sticky/src/styles.scss create mode 100644 examples/angular/column-pinning-sticky/tsconfig.app.json create mode 100644 examples/angular/column-pinning-sticky/tsconfig.json create mode 100644 examples/angular/column-pinning-sticky/tsconfig.spec.json diff --git a/examples/angular/column-pinning-sticky/.editorconfig b/examples/angular/column-pinning-sticky/.editorconfig new file mode 100644 index 0000000000..59d9a3a3e7 --- /dev/null +++ b/examples/angular/column-pinning-sticky/.editorconfig @@ -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 diff --git a/examples/angular/column-pinning-sticky/.gitignore b/examples/angular/column-pinning-sticky/.gitignore new file mode 100644 index 0000000000..0711527ef9 --- /dev/null +++ b/examples/angular/column-pinning-sticky/.gitignore @@ -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 diff --git a/examples/angular/column-pinning-sticky/.vscode/extensions.json b/examples/angular/column-pinning-sticky/.vscode/extensions.json new file mode 100644 index 0000000000..77b374577d --- /dev/null +++ b/examples/angular/column-pinning-sticky/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/examples/angular/column-pinning-sticky/.vscode/launch.json b/examples/angular/column-pinning-sticky/.vscode/launch.json new file mode 100644 index 0000000000..925af83705 --- /dev/null +++ b/examples/angular/column-pinning-sticky/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/examples/angular/column-pinning-sticky/.vscode/tasks.json b/examples/angular/column-pinning-sticky/.vscode/tasks.json new file mode 100644 index 0000000000..a298b5bd87 --- /dev/null +++ b/examples/angular/column-pinning-sticky/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/examples/angular/column-pinning-sticky/README.md b/examples/angular/column-pinning-sticky/README.md new file mode 100644 index 0000000000..5da97a87d1 --- /dev/null +++ b/examples/angular/column-pinning-sticky/README.md @@ -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. diff --git a/examples/angular/column-pinning-sticky/angular.json b/examples/angular/column-pinning-sticky/angular.json new file mode 100644 index 0000000000..c9fe837fe5 --- /dev/null +++ b/examples/angular/column-pinning-sticky/angular.json @@ -0,0 +1,83 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "basic": { + "cli": { + "cache": { + "enabled": false + } + }, + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/column-pinning-sticky", + "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": [], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "basic:build:production" + }, + "development": { + "buildTarget": "basic:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "basic:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.scss"], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": "73f296b8-52f2-4044-acca-9178df581487" + } +} diff --git a/examples/angular/column-pinning-sticky/package.json b/examples/angular/column-pinning-sticky/package.json new file mode 100644 index 0000000000..e6a128598b --- /dev/null +++ b/examples/angular/column-pinning-sticky/package.json @@ -0,0 +1,38 @@ +{ + "name": "tanstack-table-example-angular-column-pinning-sticky", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^17.3.1", + "@angular/common": "^17.3.1", + "@angular/compiler": "^17.3.1", + "@angular/core": "^17.3.1", + "@angular/forms": "^17.3.1", + "@angular/platform-browser": "^17.3.1", + "@angular/platform-browser-dynamic": "^17.3.1", + "@tanstack/angular-table": "^8.14.0", + "rxjs": "~7.8.1", + "zone.js": "~0.14.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^17.3.1", + "@angular/cli": "^17.3.1", + "@angular/compiler-cli": "^17.3.1", + "@types/jasmine": "~5.1.4", + "jasmine-core": "~5.1.2", + "karma": "~6.4.3", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.1", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "tslib": "^2.6.2", + "typescript": "5.4.5" + } +} diff --git a/examples/angular/column-pinning-sticky/src/app/app.component.html b/examples/angular/column-pinning-sticky/src/app/app.component.html new file mode 100644 index 0000000000..141fce97c0 --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/app/app.component.html @@ -0,0 +1,137 @@ +
+
+
+ +
+ + @for (column of table.getAllLeafColumns(); track column.id) { +
+ +
+ } +
+ +
+ +
+ + +
+
+ +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + +
+
+ @if (!header.isPlaceholder) { + + {{ headerValue }} + + } + + {{ + header.column.getIndex( + header.column.getIsPinned() || 'center' + ) + }} +
+ + @if (!header.isPlaceholder && header.column.getCanPin()) { +
+ @if (header.column.getIsPinned() !== 'left') { + + } + + @if (header.column.getIsPinned()) { + + } + + @if (header.column.getIsPinned() !== 'right') { + + } +
+ } + + +
+
+ + {{ cellValue }} + +
+
+
+ +
+
{{ stringifiedColumnPinning() }}
diff --git a/examples/angular/column-pinning-sticky/src/app/app.component.ts b/examples/angular/column-pinning-sticky/src/app/app.component.ts new file mode 100644 index 0000000000..c5ac8ada05 --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/app/app.component.ts @@ -0,0 +1,138 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + Column, + ColumnDef, + type ColumnOrderState, + type ColumnPinningState, + createAngularTable, + FlexRenderDirective, + getCoreRowModel, + type VisibilityState, +} from '@tanstack/angular-table' +import { makeData } from './makeData' +import { faker } from '@faker-js/faker' +import { NgStyle, NgTemplateOutlet, SlicePipe } from '@angular/common' + +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const defaultColumns: ColumnDef[] = [ + { + accessorKey: 'firstName', + id: 'firstName', + header: 'First Name', + cell: info => info.getValue(), + footer: props => props.column.id, + size: 180, + }, + { + accessorFn: row => row.lastName, + id: 'lastName', + cell: info => info.getValue(), + header: () => 'Last Name', + footer: props => props.column.id, + size: 180, + }, + { + accessorKey: 'age', + id: 'age', + header: 'Age', + footer: props => props.column.id, + size: 180, + }, + { + accessorKey: 'visits', + id: 'visits', + header: 'Visits', + footer: props => props.column.id, + size: 180, + }, + { + accessorKey: 'status', + id: 'status', + header: 'Status', + footer: props => props.column.id, + size: 180, + }, + { + accessorKey: 'progress', + id: 'progress', + header: 'Profile Progress', + footer: props => props.column.id, + size: 180, + }, +] + +@Component({ + selector: 'app-root', + standalone: true, + imports: [FlexRenderDirective, SlicePipe, NgTemplateOutlet, NgStyle], + templateUrl: './app.component.html', +}) +export class AppComponent { + readonly columns = signal([...defaultColumns]) + readonly data = signal(makeData(30)) + readonly columnVisibility = signal({}) + readonly columnOrder = signal([]) + readonly columnPinning = signal({}) + readonly split = signal(false) + + table = createAngularTable(() => ({ + data: this.data(), + columns: this.columns(), + getCoreRowModel: getCoreRowModel(), + debugTable: true, + debugHeaders: true, + debugColumns: true, + columnResizeMode: 'onChange', + })) + + stringifiedColumnPinning = computed(() => { + return JSON.stringify(this.table.getState().columnPinning) + }) + + readonly getCommonPinningStyles = ( + column: Column + ): Record => { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === 'left' && column.getIsLastColumn('left') + const isFirstRightPinnedColumn = + isPinned === 'right' && column.getIsFirstColumn('right') + + return { + boxShadow: isLastLeftPinnedColumn + ? '-4px 0 4px -4px gray inset' + : isFirstRightPinnedColumn + ? '4px 0 4px -4px gray inset' + : undefined, + left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, + right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, + opacity: isPinned ? 0.95 : 1, + position: isPinned ? 'sticky' : 'relative', + width: column.getSize(), + zIndex: isPinned ? 1 : 0, + } + } + + randomizeColumns() { + this.table.setColumnOrder( + faker.helpers.shuffle(this.table.getAllLeafColumns().map(d => d.id)) + ) + } + + rerender() { + this.data.set(makeData(5000)) + } +} diff --git a/examples/angular/column-pinning-sticky/src/app/app.config.ts b/examples/angular/column-pinning-sticky/src/app/app.config.ts new file mode 100644 index 0000000000..f27099f33c --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/app/app.config.ts @@ -0,0 +1,5 @@ +import { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [], +} diff --git a/examples/angular/column-pinning-sticky/src/app/makeData.ts b/examples/angular/column-pinning-sticky/src/app/makeData.ts new file mode 100644 index 0000000000..331dd1eb19 --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Person[] +} + +const range = (len: number) => { + const arr: number[] = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0]!, + } +} + +export function makeData(...lens: number[]) { + const makeDataLevel = (depth = 0): Person[] => { + const len = lens[depth]! + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/column-pinning-sticky/src/assets/.gitkeep b/examples/angular/column-pinning-sticky/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/angular/column-pinning-sticky/src/favicon.ico b/examples/angular/column-pinning-sticky/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 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 Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/examples/angular/column-pinning-sticky/src/index.html b/examples/angular/column-pinning-sticky/src/index.html new file mode 100644 index 0000000000..a4bb987648 --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/index.html @@ -0,0 +1,14 @@ + + + + + Basic + + + + + + + + + diff --git a/examples/angular/column-pinning-sticky/src/main.ts b/examples/angular/column-pinning-sticky/src/main.ts new file mode 100644 index 0000000000..0c3b92057c --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { AppComponent } from './app/app.component' + +bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) diff --git a/examples/angular/column-pinning-sticky/src/styles.scss b/examples/angular/column-pinning-sticky/src/styles.scss new file mode 100644 index 0000000000..2e804931bd --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/styles.scss @@ -0,0 +1,50 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +.table-container { + border: 1px solid lightgray; + overflow-x: scroll; + width: 100%; + max-width: 960px; + position: relative; +} + +table { + /* box-shadow and borders will not work with positon: sticky otherwise */ + border-collapse: separate !important; + border-spacing: 0; +} + +th { + background-color: lightgray; + border-bottom: 1px solid lightgray; + font-weight: bold; + height: 30px; + padding: 2px 4px; + position: relative; + text-align: center; +} + +td { + background-color: white; + padding: 2px 4px; +} + +.resizer { + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + height: 100%; + position: absolute; + right: 0; + top: 0; + touch-action: none; + user-select: none; + width: 5px; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} diff --git a/examples/angular/column-pinning-sticky/tsconfig.app.json b/examples/angular/column-pinning-sticky/tsconfig.app.json new file mode 100644 index 0000000000..84f1f992d2 --- /dev/null +++ b/examples/angular/column-pinning-sticky/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/examples/angular/column-pinning-sticky/tsconfig.json b/examples/angular/column-pinning-sticky/tsconfig.json new file mode 100644 index 0000000000..b58d3efc71 --- /dev/null +++ b/examples/angular/column-pinning-sticky/tsconfig.json @@ -0,0 +1,31 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "src", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/examples/angular/column-pinning-sticky/tsconfig.spec.json b/examples/angular/column-pinning-sticky/tsconfig.spec.json new file mode 100644 index 0000000000..47e3dd7551 --- /dev/null +++ b/examples/angular/column-pinning-sticky/tsconfig.spec.json @@ -0,0 +1,9 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["jasmine"] + }, + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09c7ccdacd..96e7861b5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -318,6 +318,76 @@ importers: specifier: 5.4.5 version: 5.4.5 + examples/angular/column-pinning-sticky: + dependencies: + '@angular/animations': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1) + '@angular/common': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1)(rxjs@7.8.1) + '@angular/compiler': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1) + '@angular/core': + specifier: ^17.3.1 + version: 17.3.1(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/forms': + specifier: ^17.3.1 + version: 17.3.6(@angular/common@17.3.6)(@angular/core@17.3.1)(@angular/platform-browser@17.3.6)(rxjs@7.8.1) + '@angular/platform-browser': + specifier: ^17.3.1 + version: 17.3.6(@angular/animations@17.3.6)(@angular/common@17.3.6)(@angular/core@17.3.1) + '@angular/platform-browser-dynamic': + specifier: ^17.3.1 + version: 17.3.6(@angular/common@17.3.6)(@angular/compiler@17.3.6)(@angular/core@17.3.1)(@angular/platform-browser@17.3.6) + '@tanstack/angular-table': + specifier: ^8.14.0 + version: link:../../../packages/angular-table + rxjs: + specifier: ~7.8.1 + version: 7.8.1 + zone.js: + specifier: ~0.14.4 + version: 0.14.4 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^17.3.1 + version: 17.3.6(@angular/compiler-cli@17.3.6)(@types/node@20.12.7)(karma@6.4.3)(ng-packagr@17.3.0)(typescript@5.4.5) + '@angular/cli': + specifier: ^17.3.1 + version: 17.3.6 + '@angular/compiler-cli': + specifier: ^17.3.1 + version: 17.3.6(@angular/compiler@17.3.6)(typescript@5.4.5) + '@types/jasmine': + specifier: ~5.1.4 + version: 5.1.4 + jasmine-core: + specifier: ~5.1.2 + version: 5.1.2 + karma: + specifier: ~6.4.3 + version: 6.4.3 + karma-chrome-launcher: + specifier: ~3.2.0 + version: 3.2.0 + karma-coverage: + specifier: ~2.2.1 + version: 2.2.1 + karma-jasmine: + specifier: ~5.1.0 + version: 5.1.0(karma@6.4.3) + karma-jasmine-html-reporter: + specifier: ~2.1.0 + version: 2.1.0(jasmine-core@5.1.2)(karma-jasmine@5.1.0)(karma@6.4.3) + tslib: + specifier: ^2.6.2 + version: 2.6.2 + typescript: + specifier: 5.4.5 + version: 5.4.5 + examples/angular/column-visibility: dependencies: '@angular/animations': From cbb8fa4de4528c74f1834b4cf7d809c8e4d2c789 Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Sat, 4 May 2024 16:24:17 +0200 Subject: [PATCH 11/12] add filters example --- examples/angular/filters/.editorconfig | 16 ++ examples/angular/filters/.gitignore | 42 ++++++ .../angular/filters/.vscode/extensions.json | 4 + examples/angular/filters/.vscode/launch.json | 20 +++ examples/angular/filters/.vscode/tasks.json | 42 ++++++ examples/angular/filters/README.md | 27 ++++ examples/angular/filters/angular.json | 81 ++++++++++ examples/angular/filters/package.json | 39 +++++ .../filters/src/app/app.component.html | 138 ++++++++++++++++++ .../filters/src/app/app.component.scss | 32 ++++ .../angular/filters/src/app/app.component.ts | 125 ++++++++++++++++ .../angular/filters/src/app/app.config.ts | 5 + .../src/app/debounced-input.directive.ts | 37 +++++ examples/angular/filters/src/app/filter.ts | 136 +++++++++++++++++ examples/angular/filters/src/app/makeData.ts | 48 ++++++ .../src/app/selection-column.component.ts | 43 ++++++ examples/angular/filters/src/assets/.gitkeep | 0 examples/angular/filters/src/favicon.ico | Bin 0 -> 15086 bytes examples/angular/filters/src/index.html | 14 ++ examples/angular/filters/src/main.ts | 5 + examples/angular/filters/src/styles.scss | 26 ++++ examples/angular/filters/tsconfig.app.json | 10 ++ examples/angular/filters/tsconfig.json | 30 ++++ examples/angular/filters/tsconfig.spec.json | 9 ++ .../person-table/person-table.component.ts | 11 +- pnpm-lock.yaml | 73 +++++++++ 26 files changed, 1003 insertions(+), 10 deletions(-) create mode 100644 examples/angular/filters/.editorconfig create mode 100644 examples/angular/filters/.gitignore create mode 100644 examples/angular/filters/.vscode/extensions.json create mode 100644 examples/angular/filters/.vscode/launch.json create mode 100644 examples/angular/filters/.vscode/tasks.json create mode 100644 examples/angular/filters/README.md create mode 100644 examples/angular/filters/angular.json create mode 100644 examples/angular/filters/package.json create mode 100644 examples/angular/filters/src/app/app.component.html create mode 100644 examples/angular/filters/src/app/app.component.scss create mode 100644 examples/angular/filters/src/app/app.component.ts create mode 100644 examples/angular/filters/src/app/app.config.ts create mode 100644 examples/angular/filters/src/app/debounced-input.directive.ts create mode 100644 examples/angular/filters/src/app/filter.ts create mode 100644 examples/angular/filters/src/app/makeData.ts create mode 100644 examples/angular/filters/src/app/selection-column.component.ts create mode 100644 examples/angular/filters/src/assets/.gitkeep create mode 100644 examples/angular/filters/src/favicon.ico create mode 100644 examples/angular/filters/src/index.html create mode 100644 examples/angular/filters/src/main.ts create mode 100644 examples/angular/filters/src/styles.scss create mode 100644 examples/angular/filters/tsconfig.app.json create mode 100644 examples/angular/filters/tsconfig.json create mode 100644 examples/angular/filters/tsconfig.spec.json diff --git a/examples/angular/filters/.editorconfig b/examples/angular/filters/.editorconfig new file mode 100644 index 0000000000..59d9a3a3e7 --- /dev/null +++ b/examples/angular/filters/.editorconfig @@ -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 diff --git a/examples/angular/filters/.gitignore b/examples/angular/filters/.gitignore new file mode 100644 index 0000000000..0711527ef9 --- /dev/null +++ b/examples/angular/filters/.gitignore @@ -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 diff --git a/examples/angular/filters/.vscode/extensions.json b/examples/angular/filters/.vscode/extensions.json new file mode 100644 index 0000000000..77b374577d --- /dev/null +++ b/examples/angular/filters/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/examples/angular/filters/.vscode/launch.json b/examples/angular/filters/.vscode/launch.json new file mode 100644 index 0000000000..925af83705 --- /dev/null +++ b/examples/angular/filters/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/examples/angular/filters/.vscode/tasks.json b/examples/angular/filters/.vscode/tasks.json new file mode 100644 index 0000000000..a298b5bd87 --- /dev/null +++ b/examples/angular/filters/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/examples/angular/filters/README.md b/examples/angular/filters/README.md new file mode 100644 index 0000000000..73a201f1eb --- /dev/null +++ b/examples/angular/filters/README.md @@ -0,0 +1,27 @@ +# Selection + +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. diff --git a/examples/angular/filters/angular.json b/examples/angular/filters/angular.json new file mode 100644 index 0000000000..9287417602 --- /dev/null +++ b/examples/angular/filters/angular.json @@ -0,0 +1,81 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "selection": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/filters", + "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": [], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "selection:build:production" + }, + "development": { + "buildTarget": "selection:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "selection:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.scss"], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": false, + "cache": { + "enabled": false + } + } +} diff --git a/examples/angular/filters/package.json b/examples/angular/filters/package.json new file mode 100644 index 0000000000..ba9c89a99a --- /dev/null +++ b/examples/angular/filters/package.json @@ -0,0 +1,39 @@ +{ + "name": "tanstack-table-example-angular-filters", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^17.3.1", + "@angular/common": "^17.3.1", + "@angular/compiler": "^17.3.1", + "@angular/core": "^17.3.1", + "@angular/forms": "^17.3.1", + "@angular/platform-browser": "^17.3.1", + "@angular/platform-browser-dynamic": "^17.3.1", + "@faker-js/faker": "^8.4.1", + "@tanstack/angular-table": "^8.14.0", + "rxjs": "~7.8.1", + "tslib": "^2.6.2", + "zone.js": "~0.14.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^17.3.1", + "@angular/cli": "^17.3.1", + "@angular/compiler-cli": "^17.3.1", + "@types/jasmine": "~5.1.4", + "jasmine-core": "~5.1.2", + "karma": "~6.4.3", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.1", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "5.4.5" + } +} diff --git a/examples/angular/filters/src/app/app.component.html b/examples/angular/filters/src/app/app.component.html new file mode 100644 index 0000000000..e4f5d993f0 --- /dev/null +++ b/examples/angular/filters/src/app/app.component.html @@ -0,0 +1,138 @@ +
+
+ + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + +
+ @if (!header.isPlaceholder) { +
+ + {{ headerCell }} + + + @if (header.column.getIsSorted() === 'asc') { + 🔼 + } + @if (header.column.getIsSorted() === 'desc') { + 🔽 + } +
+ + @if (header.column.getCanFilter()) { +
+ +
+ } + } +
+ + {{ renderCell }} + +
+ +
+
+ + + + + +
Page
+ + {{ table.getState().pagination.pageIndex + 1 }} of + {{ table.getPageCount() }} + +
+ + | Go to page: + + + + +
+
{{ table.getPrePaginationRowModel().rows.length }} Rows
+
+ +
+
+
{{ stringifiedFilters() }}
+
+
+ + + Age 🥳 + diff --git a/examples/angular/filters/src/app/app.component.scss b/examples/angular/filters/src/app/app.component.scss new file mode 100644 index 0000000000..cda3113f7d --- /dev/null +++ b/examples/angular/filters/src/app/app.component.scss @@ -0,0 +1,32 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.pagination-actions { + margin: 10px; + display: flex; + gap: 10px; +} diff --git a/examples/angular/filters/src/app/app.component.ts b/examples/angular/filters/src/app/app.component.ts new file mode 100644 index 0000000000..191a3a71a5 --- /dev/null +++ b/examples/angular/filters/src/app/app.component.ts @@ -0,0 +1,125 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + ColumnDef, + type ColumnFiltersState, + createAngularTable, + FlexRenderDirective, + getCoreRowModel, + getFacetedMinMaxValues, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, +} from '@tanstack/angular-table' +import { FilterComponent } from './filter' +import { makeData, type Person } from './makeData' +import { FormsModule } from '@angular/forms' +import { NgClass } from '@angular/common' + +@Component({ + selector: 'app-root', + standalone: true, + imports: [FilterComponent, FlexRenderDirective, FormsModule, NgClass], + templateUrl: './app.component.html', + styleUrl: './app.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppComponent { + readonly columnFilters = signal([]) + readonly data = signal(makeData(5000)) + + readonly columns: ColumnDef[] = [ + { + accessorKey: 'firstName', + cell: info => info.getValue(), + }, + { + accessorFn: row => row.lastName, + id: 'lastName', + cell: info => info.getValue(), + header: () => 'Last Name', + }, + { + accessorKey: 'age', + header: () => 'Age', + meta: { + filterVariant: 'range', + }, + }, + { + accessorKey: 'visits', + header: () => 'Visits', + meta: { + filterVariant: 'range', + }, + }, + { + accessorKey: 'status', + header: 'Status', + meta: { + filterVariant: 'select', + }, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + meta: { + filterVariant: 'range', + }, + }, + ] + + table = createAngularTable(() => ({ + columns: this.columns, + data: this.data(), + state: { + columnFilters: this.columnFilters(), + }, + onColumnFiltersChange: updaterOrValue => { + typeof updaterOrValue === 'function' + ? this.columnFilters.update(updaterOrValue) + : this.columnFilters.set(updaterOrValue) + }, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), //client-side filtering + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getFacetedRowModel: getFacetedRowModel(), // client-side faceting + getFacetedUniqueValues: getFacetedUniqueValues(), // generate unique values for select filter/autocomplete + getFacetedMinMaxValues: getFacetedMinMaxValues(), // generate min/max values for range filter + debugTable: true, + debugHeaders: true, + debugColumns: false, + })) + + readonly stringifiedFilters = computed(() => + JSON.stringify(this.columnFilters(), null, 2) + ) + + onPageInputChange(event: Event): void { + const inputElement = event.target as HTMLInputElement + const page = inputElement.value ? Number(inputElement.value) - 1 : 0 + this.table.setPageIndex(page) + } + + onPageSizeChange(event: any): void { + this.table.setPageSize(Number(event.target.value)) + } + + logSelectedFlatRows(): void { + console.info( + 'table.getSelectedRowModel().flatRows', + this.table.getSelectedRowModel().flatRows + ) + } + + refreshData(): void { + this.data.set(makeData(100_000)) // stress test + } +} diff --git a/examples/angular/filters/src/app/app.config.ts b/examples/angular/filters/src/app/app.config.ts new file mode 100644 index 0000000000..f27099f33c --- /dev/null +++ b/examples/angular/filters/src/app/app.config.ts @@ -0,0 +1,5 @@ +import { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [], +} diff --git a/examples/angular/filters/src/app/debounced-input.directive.ts b/examples/angular/filters/src/app/debounced-input.directive.ts new file mode 100644 index 0000000000..19d5ef1b55 --- /dev/null +++ b/examples/angular/filters/src/app/debounced-input.directive.ts @@ -0,0 +1,37 @@ +import { Directive, ElementRef, inject, input, NgZone } from '@angular/core' +import { + debounceTime, + fromEvent, + type MonoTypeOperatorFunction, + Observable, + switchMap, +} from 'rxjs' +import { outputFromObservable, toObservable } from '@angular/core/rxjs-interop' + +export function runOutsideAngular( + zone: NgZone +): MonoTypeOperatorFunction { + return source => + new Observable(subscriber => + zone.runOutsideAngular(() => source.subscribe(subscriber)) + ) +} + +@Directive({ + standalone: true, + selector: 'input[debouncedInput]', +}) +export class DebouncedInputDirective { + #ref = inject(ElementRef).nativeElement as HTMLInputElement + + readonly debounce = input(500) + readonly debounce$ = toObservable(this.debounce) + + readonly changeEvent = outputFromObservable( + this.debounce$.pipe( + switchMap(debounce => { + return fromEvent(this.#ref, 'change').pipe(debounceTime(debounce)) + }) + ) + ) +} diff --git a/examples/angular/filters/src/app/filter.ts b/examples/angular/filters/src/app/filter.ts new file mode 100644 index 0000000000..2c897cfe7d --- /dev/null +++ b/examples/angular/filters/src/app/filter.ts @@ -0,0 +1,136 @@ +import { CommonModule } from '@angular/common' +import { Component, computed, input, OnInit } from '@angular/core' +import type { Column, RowData, Table } from '@tanstack/angular-table' +import { DebouncedInputDirective } from './debounced-input.directive' + +declare module '@tanstack/angular-table' { + //allows us to define custom properties for our columns + interface ColumnMeta { + filterVariant?: 'text' | 'range' | 'select' + } +} + +@Component({ + selector: 'app-table-filter', + template: ` + @if (filterVariant() === 'range') { +
+
+ + + +
+
+
+ } @else if (filterVariant() === 'select') { + + } @else { + + @for (value of sortedUniqueValues(); track value) { + + } + + +
+ } + `, + standalone: true, + imports: [CommonModule, DebouncedInputDirective], +}) +export class FilterComponent { + column = input.required>() + + table = input.required>() + + readonly filterVariant = computed(() => { + return (this.column().columnDef.meta ?? {}).filterVariant + }) + + readonly columnFilterValue = computed(() => + this.column().getFilterValue() + ) + + readonly minRangePlaceholder = computed(() => { + return `Min ${ + this.column().getFacetedMinMaxValues()?.[0] !== undefined + ? `(${this.column().getFacetedMinMaxValues()?.[0]})` + : '' + }` + }) + + readonly maxRangePlaceholder = computed(() => { + return `Max ${ + this.column().getFacetedMinMaxValues()?.[1] + ? `(${this.column().getFacetedMinMaxValues()?.[1]})` + : '' + }` + }) + + readonly sortedUniqueValues = computed(() => { + const filterVariant = this.filterVariant() + const column = this.column() + if (filterVariant === 'range') { + return [] + } + return Array.from(column.getFacetedUniqueValues().keys()) + .sort() + .slice(0, 5000) + }) + + readonly changeMinRangeValue = (event: Event) => { + const value = (event.target as HTMLInputElement).value + this.column().setFilterValue((old: [number, number]) => { + return [value, old?.[1]] + }) + } + + readonly changeMaxRangeValue = (event: Event) => { + const value = (event.target as HTMLInputElement).value + this.column().setFilterValue((old: [number, number]) => { + return [old?.[0], value] + }) + } +} diff --git a/examples/angular/filters/src/app/makeData.ts b/examples/angular/filters/src/app/makeData.ts new file mode 100644 index 0000000000..331dd1eb19 --- /dev/null +++ b/examples/angular/filters/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Person[] +} + +const range = (len: number) => { + const arr: number[] = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0]!, + } +} + +export function makeData(...lens: number[]) { + const makeDataLevel = (depth = 0): Person[] => { + const len = lens[depth]! + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/filters/src/app/selection-column.component.ts b/examples/angular/filters/src/app/selection-column.component.ts new file mode 100644 index 0000000000..b4f3e1c008 --- /dev/null +++ b/examples/angular/filters/src/app/selection-column.component.ts @@ -0,0 +1,43 @@ +import { + type CellContext, + type HeaderContext, + injectFlexRenderContext, +} from '@tanstack/angular-table' +import { ChangeDetectionStrategy, Component } from '@angular/core' + +@Component({ + template: ` + + `, + host: { + class: 'px-1 block', + }, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TableHeadSelectionComponent { + context = injectFlexRenderContext>() +} + +@Component({ + template: ` + + `, + host: { + class: 'px-1 block', + }, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TableRowSelectionComponent { + context = injectFlexRenderContext>() +} diff --git a/examples/angular/filters/src/assets/.gitkeep b/examples/angular/filters/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/angular/filters/src/favicon.ico b/examples/angular/filters/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 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 Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/examples/angular/filters/src/index.html b/examples/angular/filters/src/index.html new file mode 100644 index 0000000000..27917d2b28 --- /dev/null +++ b/examples/angular/filters/src/index.html @@ -0,0 +1,14 @@ + + + + + Selection + + + + + + + + + diff --git a/examples/angular/filters/src/main.ts b/examples/angular/filters/src/main.ts new file mode 100644 index 0000000000..0c3b92057c --- /dev/null +++ b/examples/angular/filters/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { AppComponent } from './app/app.component' + +bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) diff --git a/examples/angular/filters/src/styles.scss b/examples/angular/filters/src/styles.scss new file mode 100644 index 0000000000..43c09e0f6b --- /dev/null +++ b/examples/angular/filters/src/styles.scss @@ -0,0 +1,26 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} diff --git a/examples/angular/filters/tsconfig.app.json b/examples/angular/filters/tsconfig.app.json new file mode 100644 index 0000000000..84f1f992d2 --- /dev/null +++ b/examples/angular/filters/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/examples/angular/filters/tsconfig.json b/examples/angular/filters/tsconfig.json new file mode 100644 index 0000000000..fd2d87ac26 --- /dev/null +++ b/examples/angular/filters/tsconfig.json @@ -0,0 +1,30 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/examples/angular/filters/tsconfig.spec.json b/examples/angular/filters/tsconfig.spec.json new file mode 100644 index 0000000000..47e3dd7551 --- /dev/null +++ b/examples/angular/filters/tsconfig.spec.json @@ -0,0 +1,9 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["jasmine"] + }, + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/examples/angular/signal-input/src/app/person-table/person-table.component.ts b/examples/angular/signal-input/src/app/person-table/person-table.component.ts index c0f88550e3..29a8622ce9 100644 --- a/examples/angular/signal-input/src/app/person-table/person-table.component.ts +++ b/examples/angular/signal-input/src/app/person-table/person-table.component.ts @@ -1,12 +1,4 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - effect, - inject, - input, - model, -} from '@angular/core' +import { ChangeDetectionStrategy, Component, input, model } from '@angular/core' import type { Person } from '../makeData' import { ColumnDef, @@ -47,7 +39,6 @@ export class PersonTableComponent { ] table = createAngularTable(() => { - const data = this.data() return { data: this.data(), columns: this.columns, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96e7861b5a..212913d9ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -458,6 +458,79 @@ importers: specifier: 5.4.5 version: 5.4.5 + examples/angular/filters: + dependencies: + '@angular/animations': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1) + '@angular/common': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1)(rxjs@7.8.1) + '@angular/compiler': + specifier: ^17.3.1 + version: 17.3.6(@angular/core@17.3.1) + '@angular/core': + specifier: ^17.3.1 + version: 17.3.1(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/forms': + specifier: ^17.3.1 + version: 17.3.6(@angular/common@17.3.6)(@angular/core@17.3.1)(@angular/platform-browser@17.3.6)(rxjs@7.8.1) + '@angular/platform-browser': + specifier: ^17.3.1 + version: 17.3.6(@angular/animations@17.3.6)(@angular/common@17.3.6)(@angular/core@17.3.1) + '@angular/platform-browser-dynamic': + specifier: ^17.3.1 + version: 17.3.6(@angular/common@17.3.6)(@angular/compiler@17.3.6)(@angular/core@17.3.1)(@angular/platform-browser@17.3.6) + '@faker-js/faker': + specifier: ^8.4.1 + version: 8.4.1 + '@tanstack/angular-table': + specifier: ^8.14.0 + version: link:../../../packages/angular-table + rxjs: + specifier: ~7.8.1 + version: 7.8.1 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + zone.js: + specifier: ~0.14.4 + version: 0.14.4 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^17.3.1 + version: 17.3.6(@angular/compiler-cli@17.3.6)(@types/node@20.12.7)(karma@6.4.3)(ng-packagr@17.3.0)(typescript@5.4.5) + '@angular/cli': + specifier: ^17.3.1 + version: 17.3.6 + '@angular/compiler-cli': + specifier: ^17.3.1 + version: 17.3.6(@angular/compiler@17.3.6)(typescript@5.4.5) + '@types/jasmine': + specifier: ~5.1.4 + version: 5.1.4 + jasmine-core: + specifier: ~5.1.2 + version: 5.1.2 + karma: + specifier: ~6.4.3 + version: 6.4.3 + karma-chrome-launcher: + specifier: ~3.2.0 + version: 3.2.0 + karma-coverage: + specifier: ~2.2.1 + version: 2.2.1 + karma-jasmine: + specifier: ~5.1.0 + version: 5.1.0(karma@6.4.3) + karma-jasmine-html-reporter: + specifier: ~2.1.0 + version: 2.1.0(jasmine-core@5.1.2)(karma-jasmine@5.1.0)(karma@6.4.3) + typescript: + specifier: 5.4.5 + version: 5.4.5 + examples/angular/grouping: dependencies: '@angular/animations': From 4c765c3764798b1519f63bdd0d00d78e36357790 Mon Sep 17 00:00:00 2001 From: riccardoperra Date: Sat, 4 May 2024 16:26:10 +0200 Subject: [PATCH 12/12] cleanup code --- .../angular/basic/src/app/app.component.scss | 0 .../angular/basic/src/app/app.component.ts | 1 - .../src/app/app.component.scss | 0 .../src/app/app.component.ts | 1 - .../filters/src/app/app.component.scss | 32 -------------- .../angular/filters/src/app/app.component.ts | 10 +---- .../src/app/selection-column.component.ts | 43 ------------------- .../{filter.ts => table-filter.component.ts} | 0 .../row-selection/src/app/app.component.scss | 32 -------------- .../row-selection/src/app/app.component.ts | 1 - 10 files changed, 1 insertion(+), 119 deletions(-) delete mode 100644 examples/angular/basic/src/app/app.component.scss delete mode 100644 examples/angular/column-visibility/src/app/app.component.scss delete mode 100644 examples/angular/filters/src/app/app.component.scss delete mode 100644 examples/angular/filters/src/app/selection-column.component.ts rename examples/angular/filters/src/app/{filter.ts => table-filter.component.ts} (100%) delete mode 100644 examples/angular/row-selection/src/app/app.component.scss diff --git a/examples/angular/basic/src/app/app.component.scss b/examples/angular/basic/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/basic/src/app/app.component.ts b/examples/angular/basic/src/app/app.component.ts index 540509e36b..4d3f8059f6 100644 --- a/examples/angular/basic/src/app/app.component.ts +++ b/examples/angular/basic/src/app/app.component.ts @@ -88,7 +88,6 @@ const defaultColumns: ColumnDef[] = [ standalone: true, imports: [RouterOutlet, FlexRenderDirective], templateUrl: './app.component.html', - styleUrl: './app.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent { diff --git a/examples/angular/column-visibility/src/app/app.component.scss b/examples/angular/column-visibility/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/column-visibility/src/app/app.component.ts b/examples/angular/column-visibility/src/app/app.component.ts index e1f7b4327b..c38a366b3c 100644 --- a/examples/angular/column-visibility/src/app/app.component.ts +++ b/examples/angular/column-visibility/src/app/app.component.ts @@ -106,7 +106,6 @@ const defaultColumns: ColumnDef[] = [ standalone: true, imports: [FlexRenderDirective], templateUrl: './app.component.html', - styleUrl: './app.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent implements OnInit { diff --git a/examples/angular/filters/src/app/app.component.scss b/examples/angular/filters/src/app/app.component.scss deleted file mode 100644 index cda3113f7d..0000000000 --- a/examples/angular/filters/src/app/app.component.scss +++ /dev/null @@ -1,32 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} - -.pagination-actions { - margin: 10px; - display: flex; - gap: 10px; -} diff --git a/examples/angular/filters/src/app/app.component.ts b/examples/angular/filters/src/app/app.component.ts index 191a3a71a5..8ad9a1f7b0 100644 --- a/examples/angular/filters/src/app/app.component.ts +++ b/examples/angular/filters/src/app/app.component.ts @@ -17,7 +17,7 @@ import { getPaginationRowModel, getSortedRowModel, } from '@tanstack/angular-table' -import { FilterComponent } from './filter' +import { FilterComponent } from './table-filter.component' import { makeData, type Person } from './makeData' import { FormsModule } from '@angular/forms' import { NgClass } from '@angular/common' @@ -27,7 +27,6 @@ import { NgClass } from '@angular/common' standalone: true, imports: [FilterComponent, FlexRenderDirective, FormsModule, NgClass], templateUrl: './app.component.html', - styleUrl: './app.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent { @@ -112,13 +111,6 @@ export class AppComponent { this.table.setPageSize(Number(event.target.value)) } - logSelectedFlatRows(): void { - console.info( - 'table.getSelectedRowModel().flatRows', - this.table.getSelectedRowModel().flatRows - ) - } - refreshData(): void { this.data.set(makeData(100_000)) // stress test } diff --git a/examples/angular/filters/src/app/selection-column.component.ts b/examples/angular/filters/src/app/selection-column.component.ts deleted file mode 100644 index b4f3e1c008..0000000000 --- a/examples/angular/filters/src/app/selection-column.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - type CellContext, - type HeaderContext, - injectFlexRenderContext, -} from '@tanstack/angular-table' -import { ChangeDetectionStrategy, Component } from '@angular/core' - -@Component({ - template: ` - - `, - host: { - class: 'px-1 block', - }, - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class TableHeadSelectionComponent { - context = injectFlexRenderContext>() -} - -@Component({ - template: ` - - `, - host: { - class: 'px-1 block', - }, - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class TableRowSelectionComponent { - context = injectFlexRenderContext>() -} diff --git a/examples/angular/filters/src/app/filter.ts b/examples/angular/filters/src/app/table-filter.component.ts similarity index 100% rename from examples/angular/filters/src/app/filter.ts rename to examples/angular/filters/src/app/table-filter.component.ts diff --git a/examples/angular/row-selection/src/app/app.component.scss b/examples/angular/row-selection/src/app/app.component.scss deleted file mode 100644 index cda3113f7d..0000000000 --- a/examples/angular/row-selection/src/app/app.component.scss +++ /dev/null @@ -1,32 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} - -.pagination-actions { - margin: 10px; - display: flex; - gap: 10px; -} diff --git a/examples/angular/row-selection/src/app/app.component.ts b/examples/angular/row-selection/src/app/app.component.ts index 5fcd2cd71d..8711fd3959 100644 --- a/examples/angular/row-selection/src/app/app.component.ts +++ b/examples/angular/row-selection/src/app/app.component.ts @@ -29,7 +29,6 @@ import { standalone: true, imports: [FilterComponent, FlexRenderDirective, FormsModule], templateUrl: './app.component.html', - styleUrl: './app.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {