@tanstack/angular-table 9.0.0-alpha.10 → 9.0.0-alpha.11

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.
@@ -0,0 +1,165 @@
1
+ import { TemplateRef, Type } from '@angular/core'
2
+ import { FlexRenderComponentInstance } from './flexRenderComponent'
3
+ import type { FlexRenderComponent } from './flexRenderComponent'
4
+ import type { FlexRenderContent } from './renderer'
5
+ import type { EmbeddedViewRef } from '@angular/core'
6
+ import type { FlexRenderComponentRef } from './flexRenderComponentFactory'
7
+
8
+ export type FlexRenderTypedContent =
9
+ | { kind: 'null' }
10
+ | {
11
+ kind: 'primitive'
12
+ content: string | number | Record<string, any>
13
+ }
14
+ | { kind: 'flexRenderComponent'; content: FlexRenderComponent<unknown> }
15
+ | { kind: 'templateRef'; content: TemplateRef<unknown> }
16
+ | { kind: 'component'; content: Type<unknown> }
17
+
18
+ export function mapToFlexRenderTypedContent(
19
+ content: FlexRenderContent<any>,
20
+ ): FlexRenderTypedContent {
21
+ if (content === null || content === undefined) {
22
+ return { kind: 'null' }
23
+ }
24
+ if (typeof content === 'string' || typeof content === 'number') {
25
+ return { kind: 'primitive', content }
26
+ }
27
+ if (content instanceof FlexRenderComponentInstance) {
28
+ return { kind: 'flexRenderComponent', content }
29
+ } else if (content instanceof TemplateRef) {
30
+ return { kind: 'templateRef', content }
31
+ } else if (content instanceof Type) {
32
+ return { kind: 'component', content }
33
+ } else {
34
+ return { kind: 'primitive', content }
35
+ }
36
+ }
37
+
38
+ export abstract class FlexRenderView<
39
+ TView extends FlexRenderComponentRef<any> | EmbeddedViewRef<unknown> | null,
40
+ > {
41
+ readonly view: TView
42
+ #previousContent: FlexRenderTypedContent | undefined
43
+ #content: FlexRenderTypedContent
44
+
45
+ protected constructor(
46
+ initialContent: Exclude<FlexRenderTypedContent, { kind: 'null' }>,
47
+ view: TView,
48
+ ) {
49
+ this.#content = initialContent
50
+ this.view = view
51
+ }
52
+
53
+ get previousContent(): FlexRenderTypedContent {
54
+ return this.#previousContent ?? { kind: 'null' }
55
+ }
56
+
57
+ get content() {
58
+ return this.#content
59
+ }
60
+
61
+ set content(content: FlexRenderTypedContent) {
62
+ this.#previousContent = this.#content
63
+ this.#content = content
64
+ }
65
+
66
+ abstract updateProps(props: Record<string, any>): void
67
+
68
+ abstract dirtyCheck(): void
69
+
70
+ abstract onDestroy(callback: Function): void
71
+
72
+ abstract unmount(): void
73
+ }
74
+
75
+ export class FlexRenderTemplateView extends FlexRenderView<
76
+ EmbeddedViewRef<unknown>
77
+ > {
78
+ constructor(
79
+ initialContent: Extract<
80
+ FlexRenderTypedContent,
81
+ { kind: 'primitive' | 'templateRef' }
82
+ >,
83
+ view: EmbeddedViewRef<unknown>,
84
+ ) {
85
+ super(initialContent, view)
86
+ }
87
+
88
+ override updateProps(props: Record<string, any>) {
89
+ this.view.markForCheck()
90
+ }
91
+
92
+ override dirtyCheck() {
93
+ // Basically a no-op. When the view is created via EmbeddedViewRef, we don't need to do any manual update
94
+ // since this type of content has a proxy as a context, then every time the root component is checked for changes,
95
+ // the property getter will be re-evaluated.
96
+ //
97
+ // If in a future we need to manually mark the view as dirty, just uncomment next line
98
+ // this.view.markForCheck()
99
+ }
100
+
101
+ override unmount() {
102
+ this.view.destroy()
103
+ }
104
+
105
+ override onDestroy(callback: Function) {
106
+ this.view.onDestroy(callback)
107
+ }
108
+ }
109
+
110
+ export class FlexRenderComponentView extends FlexRenderView<
111
+ FlexRenderComponentRef<unknown>
112
+ > {
113
+ constructor(
114
+ initialContent: Extract<
115
+ FlexRenderTypedContent,
116
+ { kind: 'component' | 'flexRenderComponent' }
117
+ >,
118
+ view: FlexRenderComponentRef<unknown>,
119
+ ) {
120
+ super(initialContent, view)
121
+ }
122
+
123
+ override updateProps(props: Record<string, any>) {
124
+ switch (this.content.kind) {
125
+ case 'component': {
126
+ this.view.setInputs(props)
127
+ break
128
+ }
129
+ case 'flexRenderComponent': {
130
+ // No-op. When FlexRenderFlags.PropsReferenceChanged is set,
131
+ // FlexRenderComponent will be updated into `dirtyCheck`.
132
+ break
133
+ }
134
+ }
135
+ }
136
+
137
+ override dirtyCheck() {
138
+ switch (this.content.kind) {
139
+ case 'component': {
140
+ // Component context is currently valuated with the cell context. Since it's reference
141
+ // shouldn't change, we force mark the component as dirty in order to re-evaluate function invocation in view.
142
+ // NOTE: this should behave like having a component with ChangeDetectionStrategy.Default
143
+ this.view.markAsDirty()
144
+ break
145
+ }
146
+ case 'flexRenderComponent': {
147
+ // Given context instance will always have a different reference than the previous one,
148
+ // so instead of recreating the entire view, we will only update the current view
149
+ if (this.view.eqType(this.content.content)) {
150
+ this.view.update(this.content.content)
151
+ }
152
+ this.view.markAsDirty()
153
+ break
154
+ }
155
+ }
156
+ }
157
+
158
+ override unmount() {
159
+ this.view.componentRef.destroy()
160
+ }
161
+
162
+ override onDestroy(callback: Function) {
163
+ this.view.componentRef.onDestroy(callback)
164
+ }
165
+ }
@@ -0,0 +1,124 @@
1
+ import {
2
+ DestroyRef,
3
+ Directive,
4
+ Injector,
5
+ InputSignal,
6
+ TemplateRef,
7
+ ViewContainerRef,
8
+ inject,
9
+ input,
10
+ } from '@angular/core'
11
+ import {
12
+ FlexRenderInputContent,
13
+ FlexViewRenderer,
14
+ } from './flex-render/renderer'
15
+ import type {
16
+ CellContext,
17
+ CellData,
18
+ HeaderContext,
19
+ RowData,
20
+ TableFeatures,
21
+ } from '@tanstack/table-core'
22
+
23
+ export {
24
+ injectFlexRenderContext,
25
+ type FlexRenderComponentProps,
26
+ } from './flex-render/context'
27
+
28
+ export type {
29
+ FlexRenderInputContent,
30
+ FlexRenderContent,
31
+ } from './flex-render/renderer'
32
+
33
+ /**
34
+ * Use this utility directive to render headers, cells, or footers with custom markup.
35
+ *
36
+ * Note: If you are rendering cell, header, or footer without custom context or other props,
37
+ * you can use the {@link FlexRenderCell} directive as shorthand instead .
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import {FlexRender} from '@tanstack/angular-table';
42
+ *
43
+ * @Component({
44
+ * imports: [FlexRender],
45
+ * template: `
46
+ * <td
47
+ * *flexRender="
48
+ * cell.column.columnDef.cell;
49
+ * props: cell.getContext();
50
+ * let cell"
51
+ * >
52
+ * {{cell}}
53
+ * </td>
54
+ *
55
+ * <th
56
+ * *flexRender="
57
+ * header.column.columnDef.header;
58
+ * props: header.getContext();
59
+ * let header"
60
+ * >
61
+ * {{header}}
62
+ * </td>
63
+ *
64
+ * <td
65
+ * *flexRender="
66
+ * footer.column.columnDef.footer;
67
+ * props: footer.getContext();
68
+ * let footer"
69
+ * >
70
+ * {{footer}}
71
+ * </td>
72
+ * `,
73
+ * })
74
+ * class App {
75
+ * }
76
+ * ```
77
+ *
78
+ * Can be imported through {@link FlexRenderDirective} or {@link FlexRender} import,
79
+ * which the latter is preferred.
80
+ */
81
+ @Directive({
82
+ selector: 'ng-template[flexRender]',
83
+ })
84
+ export class FlexRenderDirective<
85
+ TFeatures extends TableFeatures,
86
+ TRowData extends RowData,
87
+ TValue extends CellData,
88
+ TProps extends
89
+ | NonNullable<unknown>
90
+ | CellContext<TFeatures, TRowData, TValue>
91
+ | HeaderContext<TFeatures, TRowData, TValue>,
92
+ > {
93
+ readonly content: InputSignal<FlexRenderInputContent<TProps>> = input(
94
+ undefined as FlexRenderInputContent<TProps>,
95
+ { alias: 'flexRender' },
96
+ )
97
+
98
+ readonly props = input<TProps>({} as TProps, {
99
+ alias: 'flexRenderProps',
100
+ })
101
+
102
+ readonly injector = input(inject(Injector), {
103
+ alias: 'flexRenderInjector',
104
+ })
105
+
106
+ readonly #viewContainerRef = inject(ViewContainerRef)
107
+ readonly #templateRef = inject(TemplateRef)
108
+
109
+ constructor() {
110
+ const renderer = new FlexViewRenderer({
111
+ content: this.content,
112
+ props: this.props,
113
+ injector: this.injector,
114
+ templateRef: this.#templateRef,
115
+ viewContainerRef: this.#viewContainerRef,
116
+ })
117
+
118
+ renderer.mount()
119
+
120
+ inject(DestroyRef).onDestroy(() => {
121
+ renderer.destroy()
122
+ })
123
+ }
124
+ }
@@ -0,0 +1,104 @@
1
+ import { Directive, InjectionToken, inject, input } from '@angular/core'
2
+ import { Cell, CellData, RowData, TableFeatures } from '@tanstack/table-core'
3
+ import type { Signal } from '@angular/core'
4
+
5
+ /**
6
+ * DI context shape for a TanStack Table cell.
7
+ *
8
+ * This exists to make the current `Cell` injectable by any nested component/directive
9
+ * without having to pass it through inputs/props manually.
10
+ */
11
+ export interface TanStackTableCellContext<
12
+ TFeatures extends TableFeatures,
13
+ TData extends RowData,
14
+ TValue extends CellData,
15
+ > {
16
+ /** Signal that returns the current cell instance. */
17
+ cell: Signal<Cell<TFeatures, TData, TValue>>
18
+ }
19
+
20
+ /**
21
+ * Injection token that provides access to the current cell.
22
+ *
23
+ * This token is provided by the {@link TanStackTableCell} directive.
24
+ */
25
+ export const TanStackTableCellToken = new InjectionToken<
26
+ TanStackTableCellContext<any, any, any>['cell']
27
+ >('[TanStack Table] CellContext')
28
+
29
+ /**
30
+ * Provides a TanStack Table `Cell` instance in Angular DI.
31
+ *
32
+ * The cell can be injected by:
33
+ * - any descendant of an element using `[tanStackTableCell]="..."`
34
+ * - any component instantiated by `*flexRender` when the render props contains `cell`
35
+ *
36
+ * @example
37
+ * Inject from the nearest `[tanStackTableCell]`:
38
+ * ```html
39
+ * <td [tanStackTableCell]="cell">
40
+ * <app-cell-actions />
41
+ * </td>
42
+ * ```
43
+ *
44
+ * ```ts
45
+ * @Component({
46
+ * selector: 'app-cell-actions',
47
+ * template: `{{ cell().id }}`,
48
+ * })
49
+ * export class CellActionsComponent {
50
+ * readonly cell = injectTableCellContext()
51
+ * }
52
+ * ```
53
+ *
54
+ * @example
55
+ * Inject inside a component rendered via `flexRender`:
56
+ * ```ts
57
+ * @Component({
58
+ * selector: 'app-price-cell',
59
+ * template: `{{ cell().getValue() }}`,
60
+ * })
61
+ * export class PriceCellComponent {
62
+ * readonly cell = injectTableCellContext()
63
+ * }
64
+ * ```
65
+ */
66
+ @Directive({
67
+ selector: '[tanStackTableCell]',
68
+ exportAs: 'cell',
69
+ providers: [
70
+ {
71
+ provide: TanStackTableCellToken,
72
+ useFactory: () => inject(TanStackTableCell).cell,
73
+ },
74
+ ],
75
+ })
76
+ export class TanStackTableCell<
77
+ TFeatures extends TableFeatures,
78
+ TData extends RowData,
79
+ TValue extends CellData,
80
+ > implements TanStackTableCellContext<TFeatures, TData, TValue> {
81
+ /**
82
+ * The current TanStack Table cell.
83
+ *
84
+ * Provided as a required signal input so DI consumers always read the latest value.
85
+ */
86
+ readonly cell = input.required<Cell<TFeatures, TData, TValue>>({
87
+ alias: 'tanStackTableCell',
88
+ })
89
+ }
90
+
91
+ /**
92
+ * Injects the current TanStack Table cell signal.
93
+ *
94
+ * Available when:
95
+ * - there is a nearest `[tanStackTableCell]` directive in the DI tree, or
96
+ * - the caller is rendered via `*flexRender` with render props containing `cell`
97
+ */
98
+ export function injectTableCellContext<
99
+ TFeatures extends TableFeatures,
100
+ TData extends RowData,
101
+ TValue extends CellData,
102
+ >(): TanStackTableCellContext<TFeatures, TData, TValue>['cell'] {
103
+ return inject(TanStackTableCellToken)
104
+ }