@tanstack/angular-table 9.0.0-alpha.4 → 9.0.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/dist/README.md +117 -0
- package/dist/fesm2022/tanstack-angular-table-static-functions.mjs +6 -0
- package/dist/fesm2022/tanstack-angular-table-static-functions.mjs.map +1 -0
- package/dist/fesm2022/tanstack-angular-table.mjs +1225 -258
- package/dist/fesm2022/tanstack-angular-table.mjs.map +1 -1
- package/dist/types/tanstack-angular-table-static-functions.d.ts +1 -0
- package/dist/types/tanstack-angular-table.d.ts +795 -0
- package/package.json +37 -18
- package/src/flex-render/context.ts +14 -0
- package/src/flex-render/flags.ts +34 -0
- package/src/flex-render/flexRenderComponent.ts +288 -0
- package/src/flex-render/flexRenderComponentFactory.ts +241 -0
- package/src/flex-render/renderer.ts +393 -0
- package/src/flex-render/view.ts +207 -0
- package/src/flexRender.ts +124 -0
- package/src/helpers/cell.ts +104 -0
- package/src/helpers/createTableHook.ts +480 -0
- package/src/helpers/flexRenderCell.ts +136 -0
- package/src/helpers/header.ts +99 -0
- package/src/helpers/table.ts +87 -0
- package/src/index.ts +21 -70
- package/src/injectTable.ts +238 -0
- package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
- package/static-functions/index.ts +1 -0
- package/static-functions/ng-package.json +6 -0
- package/dist/esm2022/flex-render.mjs +0 -150
- package/dist/esm2022/index.mjs +0 -48
- package/dist/esm2022/lazy-signal-initializer.mjs +0 -43
- package/dist/esm2022/proxy.mjs +0 -83
- package/dist/esm2022/tanstack-angular-table.mjs +0 -5
- package/dist/flex-render.d.ts +0 -31
- package/dist/index.d.ts +0 -5
- package/dist/lazy-signal-initializer.d.ts +0 -5
- package/dist/proxy.d.ts +0 -3
- package/src/__tests__/createAngularTable.test.ts +0 -95
- package/src/__tests__/flex-render.test.ts +0 -178
- package/src/__tests__/lazy-init.test.ts +0 -124
- package/src/__tests__/test-setup.ts +0 -12
- package/src/__tests__/test-utils.ts +0 -62
- package/src/flex-render.ts +0 -187
- package/src/proxy.ts +0 -97
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DestroyRef,
|
|
3
|
+
Directive,
|
|
4
|
+
Injector,
|
|
5
|
+
TemplateRef,
|
|
6
|
+
ViewContainerRef,
|
|
7
|
+
computed,
|
|
8
|
+
inject,
|
|
9
|
+
input,
|
|
10
|
+
} from '@angular/core'
|
|
11
|
+
import {
|
|
12
|
+
Cell,
|
|
13
|
+
CellData,
|
|
14
|
+
Header,
|
|
15
|
+
RowData,
|
|
16
|
+
TableFeatures,
|
|
17
|
+
} from '@tanstack/table-core'
|
|
18
|
+
import { FlexViewRenderer } from '../flex-render/renderer'
|
|
19
|
+
import type { FlexRenderInputContent } from '../flex-render/renderer'
|
|
20
|
+
import type { CellContext, HeaderContext } from '@tanstack/table-core'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Simplified directive wrapper of `*flexRender`.
|
|
24
|
+
*
|
|
25
|
+
* Use this utility component to render headers, cells, or footers with custom markup.
|
|
26
|
+
*
|
|
27
|
+
* Only one prop (`cell`, `header`, or `footer`) may be passed based on the used selector.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```html
|
|
31
|
+
* <td *flexRenderCell="cell; let cell">{{cell}}</td>
|
|
32
|
+
* <th *flexRenderHeader="header; let header">{{header}}</th>
|
|
33
|
+
* <th *flexRenderFooter="footer; let footer">{{footer}}</th>
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* This replaces calling `*flexRender` directly like this:
|
|
37
|
+
* ```html
|
|
38
|
+
* <td *flexRender="cell.column.columnDef.cell; props: cell.getContext(); let cell">{{cell}}</td>
|
|
39
|
+
* <td *flexRender="header.column.columnDef.header; props: header.getContext(); let header">{{header}}</td>
|
|
40
|
+
* <td *flexRender="footer.column.columnDef.footer; props: footer.getContext(); let footer">{{footer}}</td>
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Can be imported through {@link FlexRenderCell} or {@link FlexRender} import,
|
|
44
|
+
* which the latter is preferred.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import {FlexRender} from '@tanstack/angular-table
|
|
49
|
+
*
|
|
50
|
+
* @Component({
|
|
51
|
+
* // ...
|
|
52
|
+
* imports: [
|
|
53
|
+
* FlexRender
|
|
54
|
+
* ]
|
|
55
|
+
* })
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
@Directive({
|
|
59
|
+
selector:
|
|
60
|
+
'ng-template[flexRenderCell], ng-template[flexRenderFooter], ng-template[flexRenderHeader]',
|
|
61
|
+
})
|
|
62
|
+
export class FlexRenderCell<
|
|
63
|
+
TFeatures extends TableFeatures,
|
|
64
|
+
TData extends RowData,
|
|
65
|
+
TValue extends CellData,
|
|
66
|
+
> {
|
|
67
|
+
readonly cell = input<Cell<TFeatures, TData, TValue>>(undefined, {
|
|
68
|
+
alias: 'flexRenderCell',
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
readonly header = input<Header<TFeatures, TData, TValue>>(undefined, {
|
|
72
|
+
alias: 'flexRenderHeader',
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
readonly footer = input<Header<TFeatures, TData, TValue>>(undefined, {
|
|
76
|
+
alias: 'flexRenderFooter',
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
readonly #renderData = computed<
|
|
80
|
+
| [
|
|
81
|
+
content: FlexRenderInputContent<CellContext<TFeatures, TData, TValue>>,
|
|
82
|
+
props: CellContext<TFeatures, TData, TValue>,
|
|
83
|
+
]
|
|
84
|
+
| [
|
|
85
|
+
content: FlexRenderInputContent<
|
|
86
|
+
HeaderContext<TFeatures, TData, TValue>
|
|
87
|
+
>,
|
|
88
|
+
props: HeaderContext<TFeatures, TData, TValue>,
|
|
89
|
+
]
|
|
90
|
+
| [content: null, props: null]
|
|
91
|
+
>(
|
|
92
|
+
() => {
|
|
93
|
+
const cell = this.cell()
|
|
94
|
+
const header = this.header()
|
|
95
|
+
const footer = this.footer()
|
|
96
|
+
if (cell) {
|
|
97
|
+
return [cell.column.columnDef.cell, cell.getContext()]
|
|
98
|
+
}
|
|
99
|
+
if (header) {
|
|
100
|
+
return [header.column.columnDef.header, header.getContext()]
|
|
101
|
+
}
|
|
102
|
+
if (footer) {
|
|
103
|
+
return [footer.column.columnDef.footer, footer.getContext()]
|
|
104
|
+
}
|
|
105
|
+
return [null, null]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
equal: (a, b) => {
|
|
109
|
+
return a[0] === b[0] && a[1] === b[1]
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
readonly #injector = inject(Injector)
|
|
115
|
+
readonly #templateRef = inject(TemplateRef)
|
|
116
|
+
readonly #viewContainerRef = inject(ViewContainerRef)
|
|
117
|
+
|
|
118
|
+
constructor() {
|
|
119
|
+
const content = computed(() => this.#renderData()[0])
|
|
120
|
+
const props = computed(() => this.#renderData()[1])
|
|
121
|
+
|
|
122
|
+
const renderer = new FlexViewRenderer<TFeatures, TData, TValue, any>({
|
|
123
|
+
content: content,
|
|
124
|
+
props: props,
|
|
125
|
+
injector: () => this.#injector,
|
|
126
|
+
templateRef: this.#templateRef,
|
|
127
|
+
viewContainerRef: this.#viewContainerRef,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
renderer.mount()
|
|
131
|
+
|
|
132
|
+
inject(DestroyRef).onDestroy(() => {
|
|
133
|
+
renderer.destroy()
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Directive, InjectionToken, inject, input } from '@angular/core'
|
|
2
|
+
import { CellData, Header, RowData, TableFeatures } from '@tanstack/table-core'
|
|
3
|
+
import type { Signal } from '@angular/core'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DI context shape for a TanStack Table header.
|
|
7
|
+
*
|
|
8
|
+
* This exists to make the current `Header` injectable by any nested component/directive
|
|
9
|
+
* without passing it through inputs/props.
|
|
10
|
+
*/
|
|
11
|
+
export interface TanStackTableHeaderContext<
|
|
12
|
+
TFeatures extends TableFeatures,
|
|
13
|
+
TData extends RowData,
|
|
14
|
+
TValue extends CellData,
|
|
15
|
+
> {
|
|
16
|
+
/** Signal that returns the current header instance. */
|
|
17
|
+
header: Signal<Header<TFeatures, TData, TValue>>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Injection token that provides access to the current header.
|
|
22
|
+
*
|
|
23
|
+
* This token is provided by the {@link TanStackTableHeader} directive.
|
|
24
|
+
*/
|
|
25
|
+
export const TanStackTableHeaderToken = new InjectionToken<
|
|
26
|
+
TanStackTableHeaderContext<any, any, any>['header']
|
|
27
|
+
>('[TanStack Table] HeaderContext')
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Provides a TanStack Table `Header` instance in Angular DI.
|
|
31
|
+
*
|
|
32
|
+
* The header can be injected by:
|
|
33
|
+
* - any descendant of an element using `[tanStackTableHeader]="..."`
|
|
34
|
+
* - any component instantiated by `*flexRender` when the render props contains `header`
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```html
|
|
38
|
+
* <th [tanStackTableHeader]="header">
|
|
39
|
+
* <app-sort-indicator />
|
|
40
|
+
* </th>
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* @Component({
|
|
45
|
+
* selector: 'app-sort-indicator',
|
|
46
|
+
* template: `
|
|
47
|
+
* <button (click)="toggle()">
|
|
48
|
+
* {{ header().column.id }}
|
|
49
|
+
* </button>
|
|
50
|
+
* `,
|
|
51
|
+
* })
|
|
52
|
+
* export class SortIndicatorComponent {
|
|
53
|
+
* readonly header = injectTableHeaderContext()
|
|
54
|
+
*
|
|
55
|
+
* toggle() {
|
|
56
|
+
* this.header().column.toggleSorting()
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
@Directive({
|
|
62
|
+
selector: '[tanStackTableHeader]',
|
|
63
|
+
exportAs: 'header',
|
|
64
|
+
providers: [
|
|
65
|
+
{
|
|
66
|
+
provide: TanStackTableHeaderToken,
|
|
67
|
+
useFactory: () => inject(TanStackTableHeader).header,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
})
|
|
71
|
+
export class TanStackTableHeader<
|
|
72
|
+
TFeatures extends TableFeatures,
|
|
73
|
+
TData extends RowData,
|
|
74
|
+
TValue extends CellData,
|
|
75
|
+
> implements TanStackTableHeaderContext<TFeatures, TData, TValue> {
|
|
76
|
+
/**
|
|
77
|
+
* The current TanStack Table header.
|
|
78
|
+
*
|
|
79
|
+
* Provided as a required signal input so DI consumers always read the latest value.
|
|
80
|
+
*/
|
|
81
|
+
readonly header = input.required<Header<TFeatures, TData, TValue>>({
|
|
82
|
+
alias: 'tanStackTableHeader',
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Injects the current TanStack Table header signal.
|
|
88
|
+
*
|
|
89
|
+
* Available when:
|
|
90
|
+
* - there is a nearest `[tanStackTableHeader]` directive in the DI tree, or
|
|
91
|
+
* - the caller is rendered via `*flexRender` with render props containing `header`
|
|
92
|
+
*/
|
|
93
|
+
export function injectTableHeaderContext<
|
|
94
|
+
TFeatures extends TableFeatures,
|
|
95
|
+
TData extends RowData,
|
|
96
|
+
TValue extends CellData,
|
|
97
|
+
>(): TanStackTableHeaderContext<TFeatures, TData, TValue>['header'] {
|
|
98
|
+
return inject(TanStackTableHeaderToken)
|
|
99
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Directive, InjectionToken, inject, input } from '@angular/core'
|
|
2
|
+
import { RowData, TableFeatures, TableState } from '@tanstack/table-core'
|
|
3
|
+
import { AngularTable } from '../injectTable'
|
|
4
|
+
import type { Signal } from '@angular/core'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Injection token that provides access to the current {@link AngularTable} instance.
|
|
8
|
+
*
|
|
9
|
+
* This token is provided by the {@link TanStackTable} directive.
|
|
10
|
+
*/
|
|
11
|
+
export const TanStackTableToken = new InjectionToken<
|
|
12
|
+
Signal<AngularTable<any, any>>
|
|
13
|
+
>('[TanStack Table] Table Context')
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Provides a TanStack Table instance (`AngularTable`) in Angular DI.
|
|
17
|
+
*
|
|
18
|
+
* The table can be injected by:
|
|
19
|
+
* - any descendant of an element using `[tanStackTable]="..."`
|
|
20
|
+
* - any component instantiated by `*flexRender` when the render props contains `table`
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```html
|
|
24
|
+
* <div [tanStackTable]="table">
|
|
25
|
+
* <app-pagination />
|
|
26
|
+
* </div>
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* @Component({
|
|
31
|
+
* selector: 'app-pagination',
|
|
32
|
+
* template: `
|
|
33
|
+
* <button (click)="prev()" [disabled]="!table().getCanPreviousPage()">Prev</button>
|
|
34
|
+
* <button (click)="next()" [disabled]="!table().getCanNextPage()">Next</button>
|
|
35
|
+
* `,
|
|
36
|
+
* })
|
|
37
|
+
* export class PaginationComponent {
|
|
38
|
+
* readonly table = injectTableContext()
|
|
39
|
+
*
|
|
40
|
+
* prev() {
|
|
41
|
+
* this.table().previousPage()
|
|
42
|
+
* }
|
|
43
|
+
* next() {
|
|
44
|
+
* this.table().nextPage()
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
@Directive({
|
|
50
|
+
selector: '[tanStackTable]',
|
|
51
|
+
exportAs: 'table',
|
|
52
|
+
providers: [
|
|
53
|
+
{
|
|
54
|
+
provide: TanStackTableToken,
|
|
55
|
+
useFactory: () => inject(TanStackTable).table,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
})
|
|
59
|
+
export class TanStackTable<
|
|
60
|
+
TFeatures extends TableFeatures,
|
|
61
|
+
TData extends RowData,
|
|
62
|
+
TSelected extends {} = TableState<TFeatures>,
|
|
63
|
+
> {
|
|
64
|
+
/**
|
|
65
|
+
* The current TanStack Table instance.
|
|
66
|
+
*
|
|
67
|
+
* Provided as a required signal input so DI consumers always read the latest value.
|
|
68
|
+
*/
|
|
69
|
+
readonly table = input.required<AngularTable<TFeatures, TData, TSelected>>({
|
|
70
|
+
alias: 'tanStackTable',
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Injects the current TanStack Table instance signal.
|
|
76
|
+
*
|
|
77
|
+
* Available when:
|
|
78
|
+
* - there is a nearest `[tanStackTable]` directive in the DI tree, or
|
|
79
|
+
* - the caller is rendered via `*flexRender` with render props containing `table`
|
|
80
|
+
*/
|
|
81
|
+
export function injectTableContext<
|
|
82
|
+
TFeatures extends TableFeatures,
|
|
83
|
+
TData extends RowData,
|
|
84
|
+
TSelected extends {} = TableState<TFeatures>,
|
|
85
|
+
>(): Signal<AngularTable<TFeatures, TData, TSelected>> {
|
|
86
|
+
return inject(TanStackTableToken)
|
|
87
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,73 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
RowData,
|
|
4
|
-
TableOptions,
|
|
5
|
-
TableOptionsResolved,
|
|
6
|
-
TableState,
|
|
7
|
-
createTable,
|
|
8
|
-
type Table,
|
|
9
|
-
} from '@tanstack/table-core'
|
|
10
|
-
import { lazyInit } from './lazy-signal-initializer'
|
|
11
|
-
import { proxifyTable } from './proxy'
|
|
1
|
+
import { FlexRenderCell } from './helpers/flexRenderCell'
|
|
2
|
+
import { FlexRenderDirective } from './flexRender'
|
|
12
3
|
|
|
13
4
|
export * from '@tanstack/table-core'
|
|
14
5
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// By default, manage table state here using the table's initial state
|
|
36
|
-
const state = signal<TableState>(table.initialState)
|
|
37
|
-
|
|
38
|
-
// Compose table options using computed.
|
|
39
|
-
// This is to allow `tableSignal` to listen and set table option
|
|
40
|
-
const updatedOptions = computed<TableOptionsResolved<TData>>(() => {
|
|
41
|
-
// listen to table state changed
|
|
42
|
-
const tableState = state()
|
|
43
|
-
// listen to input options changed
|
|
44
|
-
const tableOptions = options()
|
|
45
|
-
return {
|
|
46
|
-
...table.options,
|
|
47
|
-
...resolvedOptions,
|
|
48
|
-
...tableOptions,
|
|
49
|
-
state: { ...tableState, ...tableOptions.state },
|
|
50
|
-
onStateChange: updater => {
|
|
51
|
-
const value =
|
|
52
|
-
updater instanceof Function ? updater(tableState) : updater
|
|
53
|
-
state.set(value)
|
|
54
|
-
resolvedOptions.onStateChange?.(updater)
|
|
55
|
-
},
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
// convert table instance to signal for proxify to listen to any table state and options changes
|
|
60
|
-
const tableSignal = computed(
|
|
61
|
-
() => {
|
|
62
|
-
table.setOptions(updatedOptions())
|
|
63
|
-
return table
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
equal: () => false,
|
|
67
|
-
}
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
// proxify Table instance to provide ability for consumer to listen to any table state changes
|
|
71
|
-
return proxifyTable(tableSignal)
|
|
72
|
-
})
|
|
73
|
-
}
|
|
6
|
+
export * from './flexRender'
|
|
7
|
+
export * from './injectTable'
|
|
8
|
+
export * from './flex-render/flexRenderComponent'
|
|
9
|
+
|
|
10
|
+
export * from './helpers/cell'
|
|
11
|
+
export * from './helpers/header'
|
|
12
|
+
export * from './helpers/table'
|
|
13
|
+
export * from './helpers/createTableHook'
|
|
14
|
+
export * from './helpers/flexRenderCell'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Constant helper to import FlexRender directives.
|
|
18
|
+
*
|
|
19
|
+
* You should prefer to use this constant over importing the directives separately,
|
|
20
|
+
* as it ensures you always have the correct set of directives over library updates.
|
|
21
|
+
*
|
|
22
|
+
* @see {@link FlexRenderDirective} and {@link FlexRenderCell} for more details on the directives included in this export.
|
|
23
|
+
*/
|
|
24
|
+
export const FlexRender = [FlexRenderDirective, FlexRenderCell] as const
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injector,
|
|
3
|
+
assertInInjectionContext,
|
|
4
|
+
computed,
|
|
5
|
+
effect,
|
|
6
|
+
inject,
|
|
7
|
+
signal,
|
|
8
|
+
untracked,
|
|
9
|
+
} from '@angular/core'
|
|
10
|
+
import {
|
|
11
|
+
constructReactivityFeature,
|
|
12
|
+
constructTable,
|
|
13
|
+
} from '@tanstack/table-core'
|
|
14
|
+
import { injectSelector } from '@tanstack/angular-store'
|
|
15
|
+
import { lazyInit } from './lazySignalInitializer'
|
|
16
|
+
import type { Atom, ReadonlyAtom } from '@tanstack/angular-store'
|
|
17
|
+
import type {
|
|
18
|
+
RowData,
|
|
19
|
+
Table,
|
|
20
|
+
TableFeatures,
|
|
21
|
+
TableOptions,
|
|
22
|
+
TableState,
|
|
23
|
+
} from '@tanstack/table-core'
|
|
24
|
+
import type { Signal, ValueEqualityFn } from '@angular/core'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Store mode: pass `selector` (required) to project from full table state.
|
|
28
|
+
* Source mode: pass `source` (atom or store); omit `selector` for the whole value
|
|
29
|
+
* (identity), or pass `selector` to project. Split overloads match React `Subscribe`
|
|
30
|
+
* inference.
|
|
31
|
+
*/
|
|
32
|
+
export interface AngularTableComputed<TFeatures extends TableFeatures> {
|
|
33
|
+
<TSourceValue>(props: {
|
|
34
|
+
source: Atom<TSourceValue> | ReadonlyAtom<TSourceValue>
|
|
35
|
+
selector?: undefined
|
|
36
|
+
equal?: ValueEqualityFn<TSourceValue>
|
|
37
|
+
}): Signal<Readonly<TSourceValue>>
|
|
38
|
+
<TSourceValue, TSubSelected>(props: {
|
|
39
|
+
source: Atom<TSourceValue> | ReadonlyAtom<TSourceValue>
|
|
40
|
+
selector: (state: TSourceValue) => TSubSelected
|
|
41
|
+
equal?: ValueEqualityFn<TSubSelected>
|
|
42
|
+
}): Signal<Readonly<TSubSelected>>
|
|
43
|
+
<TSubSelected>(props: {
|
|
44
|
+
selector: (state: TableState<TFeatures>) => TSubSelected
|
|
45
|
+
equal?: ValueEqualityFn<TSubSelected>
|
|
46
|
+
}): Signal<Readonly<TSubSelected>>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type AngularTable<
|
|
50
|
+
TFeatures extends TableFeatures,
|
|
51
|
+
TData extends RowData,
|
|
52
|
+
TSelected = TableState<TFeatures>,
|
|
53
|
+
> = Table<TFeatures, TData> & {
|
|
54
|
+
/**
|
|
55
|
+
* The selected state from the table store, based on the selector provided.
|
|
56
|
+
*/
|
|
57
|
+
readonly state: Signal<Readonly<TSelected>>
|
|
58
|
+
/**
|
|
59
|
+
* A signal that returns the entire table instance. Will update on table/options change.
|
|
60
|
+
*/
|
|
61
|
+
readonly value: Signal<AngularTable<TFeatures, TData, TSelected>>
|
|
62
|
+
/**
|
|
63
|
+
* Alias: **`Subscribe`** — same function reference as `computed` (naming parity with other adapters).
|
|
64
|
+
*/
|
|
65
|
+
computed: AngularTableComputed<TFeatures>
|
|
66
|
+
Subscribe: AngularTableComputed<TFeatures>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates and returns an Angular-reactive table instance.
|
|
71
|
+
*
|
|
72
|
+
* The initializer is intentionally re-evaluated whenever any signal read inside it changes.
|
|
73
|
+
* This is how the adapter keeps the table in sync with Angular's reactivity model.
|
|
74
|
+
*
|
|
75
|
+
* Because of that behavior, keep expensive/static values (for example `columns`, feature setup, row models)
|
|
76
|
+
* as stable references outside the initializer, and only read reactive state (`data()`, pagination/filter/sorting signals, etc.)
|
|
77
|
+
* inside it.
|
|
78
|
+
*
|
|
79
|
+
* The returned table is also signal-reactive: table state and table APIs are wired for Angular signals, so you can safely consume table methods inside `computed(...)` and `effect(...)`.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* 1. Register the table features you need
|
|
83
|
+
* ```ts
|
|
84
|
+
* // Register only the features you need
|
|
85
|
+
* import {tableFeatures, rowPaginationFeature} from '@tanstack/angular-table';
|
|
86
|
+
* const _features = tableFeatures({
|
|
87
|
+
* rowPaginationFeature,
|
|
88
|
+
* // ...all other features you need
|
|
89
|
+
* })
|
|
90
|
+
*
|
|
91
|
+
* // Use all table core features
|
|
92
|
+
* import {stockFeatures} from '@tanstack/angular-table';
|
|
93
|
+
* const _features = tableFeatures(stockFeatures);
|
|
94
|
+
* ```
|
|
95
|
+
* 2. Prepare the table columns
|
|
96
|
+
* ```ts
|
|
97
|
+
* import {ColumnDef} from '@tanstack/angular-table';
|
|
98
|
+
*
|
|
99
|
+
* type MyData = {}
|
|
100
|
+
*
|
|
101
|
+
* const columns: ColumnDef<typeof _features, MyData>[] = [
|
|
102
|
+
* // ...column definitions
|
|
103
|
+
* ]
|
|
104
|
+
*
|
|
105
|
+
* // or using createColumnHelper
|
|
106
|
+
* import {createColumnHelper} from '@tanstack/angular-table';
|
|
107
|
+
* const columnHelper = createColumnHelper<typeof _features, MyData>();
|
|
108
|
+
* const columns = columnHelper.columns([
|
|
109
|
+
* columnHelper.accessor(...),
|
|
110
|
+
* // ...other columns
|
|
111
|
+
* ])
|
|
112
|
+
* ```
|
|
113
|
+
* 3. Create the table instance with `injectTable`
|
|
114
|
+
* ```ts
|
|
115
|
+
* const table = injectTable(() => {
|
|
116
|
+
* // ...table options,
|
|
117
|
+
* _features,
|
|
118
|
+
* columns: columns,
|
|
119
|
+
* data: myDataSignal(),
|
|
120
|
+
* })
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @returns An Angular-reactive TanStack Table instance.
|
|
124
|
+
*/
|
|
125
|
+
export function injectTable<
|
|
126
|
+
TFeatures extends TableFeatures,
|
|
127
|
+
TData extends RowData,
|
|
128
|
+
TSelected = TableState<TFeatures>,
|
|
129
|
+
>(
|
|
130
|
+
options: () => TableOptions<TFeatures, TData>,
|
|
131
|
+
selector: (state: TableState<TFeatures>) => TSelected = (state) =>
|
|
132
|
+
state as TSelected,
|
|
133
|
+
): AngularTable<TFeatures, TData, TSelected> {
|
|
134
|
+
assertInInjectionContext(injectTable)
|
|
135
|
+
const injector = inject(Injector)
|
|
136
|
+
const stateNotifier = signal(0)
|
|
137
|
+
const angularReactivityFeature = constructReactivityFeature({
|
|
138
|
+
stateNotifier: () => stateNotifier(),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
return lazyInit(() => {
|
|
142
|
+
const resolvedOptions: TableOptions<TFeatures, TData> = {
|
|
143
|
+
...options(),
|
|
144
|
+
_features: {
|
|
145
|
+
...options()._features,
|
|
146
|
+
angularReactivityFeature,
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const table = constructTable(resolvedOptions) as AngularTable<
|
|
151
|
+
TFeatures,
|
|
152
|
+
TData,
|
|
153
|
+
TSelected
|
|
154
|
+
>
|
|
155
|
+
const tableState = injectSelector(table.store, (state) => state, {
|
|
156
|
+
injector,
|
|
157
|
+
})
|
|
158
|
+
const tableOptions = injectSelector(table.optionsStore, (state) => state, {
|
|
159
|
+
injector,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const updatedOptions = computed<TableOptions<TFeatures, TData>>(() => {
|
|
163
|
+
const tableOptionsValue = options()
|
|
164
|
+
const result: TableOptions<TFeatures, TData> = {
|
|
165
|
+
...untracked(() => table.options),
|
|
166
|
+
...tableOptionsValue,
|
|
167
|
+
_features: { ...tableOptionsValue._features, angularReactivityFeature },
|
|
168
|
+
}
|
|
169
|
+
if (tableOptionsValue.state) {
|
|
170
|
+
result.state = tableOptionsValue.state
|
|
171
|
+
}
|
|
172
|
+
return result
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
effect(
|
|
176
|
+
() => {
|
|
177
|
+
const newOptions = updatedOptions()
|
|
178
|
+
untracked(() => table.setOptions(newOptions))
|
|
179
|
+
},
|
|
180
|
+
{ injector, debugName: 'tableOptionsUpdate' },
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
let isMount = true
|
|
184
|
+
effect(
|
|
185
|
+
() => {
|
|
186
|
+
void [tableOptions(), tableState()]
|
|
187
|
+
if (!isMount) untracked(() => stateNotifier.update((n) => n + 1))
|
|
188
|
+
isMount && (isMount = false)
|
|
189
|
+
},
|
|
190
|
+
{ injector, debugName: 'tableStateNotifier' },
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
const computedFn = function computedSubscribe(props: {
|
|
194
|
+
source?: Atom<unknown> | ReadonlyAtom<unknown>
|
|
195
|
+
selector?: (state: unknown) => unknown
|
|
196
|
+
equal?: ValueEqualityFn<unknown>
|
|
197
|
+
}) {
|
|
198
|
+
if (props.source !== undefined) {
|
|
199
|
+
return injectSelector(
|
|
200
|
+
props.source,
|
|
201
|
+
props.selector ?? ((value) => value),
|
|
202
|
+
{
|
|
203
|
+
injector,
|
|
204
|
+
...(props.equal && { compare: props.equal }),
|
|
205
|
+
},
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
return injectSelector(table.store, props.selector, {
|
|
209
|
+
injector,
|
|
210
|
+
...(props.equal && { compare: props.equal }),
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
table.computed = computedFn as AngularTable<
|
|
214
|
+
TFeatures,
|
|
215
|
+
TData,
|
|
216
|
+
TSelected
|
|
217
|
+
>['computed']
|
|
218
|
+
table.Subscribe = computedFn as AngularTable<
|
|
219
|
+
TFeatures,
|
|
220
|
+
TData,
|
|
221
|
+
TSelected
|
|
222
|
+
>['Subscribe']
|
|
223
|
+
|
|
224
|
+
Object.defineProperty(table, 'state', {
|
|
225
|
+
value: injectSelector(table.store, selector, { injector }),
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
Object.defineProperty(table, 'value', {
|
|
229
|
+
value: computed(() => {
|
|
230
|
+
tableOptions()
|
|
231
|
+
tableState()
|
|
232
|
+
return table
|
|
233
|
+
}),
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
return table
|
|
237
|
+
})
|
|
238
|
+
}
|
|
@@ -2,7 +2,7 @@ import { untracked } from '@angular/core'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Implementation from @tanstack/angular-query
|
|
5
|
-
* {
|
|
5
|
+
* {https://github.com/TanStack/query/blob/main/packages/angular-query-experimental/src/util/lazy-init/lazy-init.ts}
|
|
6
6
|
*/
|
|
7
7
|
export function lazyInit<T extends object>(initializer: () => T): T {
|
|
8
8
|
let object: T | null = null
|
|
@@ -18,7 +18,7 @@ export function lazyInit<T extends object>(initializer: () => T): T {
|
|
|
18
18
|
const table = () => {}
|
|
19
19
|
|
|
20
20
|
return new Proxy<T>(table as T, {
|
|
21
|
-
apply(target: T, thisArg: any, argArray: any
|
|
21
|
+
apply(target: T, thisArg: any, argArray: Array<any>): any {
|
|
22
22
|
initializeObject()
|
|
23
23
|
if (typeof object === 'function') {
|
|
24
24
|
return Reflect.apply(object, thisArg, argArray)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@tanstack/table-core/static-functions'
|