@tanstack/angular-table 9.0.0-alpha.9 → 9.0.0-beta.2
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 +127 -0
- package/dist/README.md +127 -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 +1337 -239
- 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 +793 -0
- package/package.json +39 -19
- package/skills/angular/angular-rendering-directives/SKILL.md +415 -0
- package/skills/angular/angular-rendering-directives/references/content-shapes.md +142 -0
- package/skills/angular/angular-rendering-directives/references/create-table-hook-registries.md +89 -0
- package/skills/angular/angular-rendering-directives/references/di-tokens.md +171 -0
- package/skills/angular/angular-rendering-directives/references/flex-render-component-options.md +64 -0
- package/skills/angular/client-to-server/SKILL.md +467 -0
- package/skills/angular/compose-with-tanstack-query/SKILL.md +482 -0
- package/skills/angular/compose-with-tanstack-store/SKILL.md +397 -0
- package/skills/angular/compose-with-tanstack-virtual/SKILL.md +400 -0
- package/skills/angular/getting-started/SKILL.md +496 -0
- package/skills/angular/getting-started/references/feature-row-model-mapping.md +48 -0
- package/skills/angular/migrate-v8-to-v9/SKILL.md +419 -0
- package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +261 -0
- package/skills/angular/production-readiness/SKILL.md +469 -0
- package/skills/angular/table-state/SKILL.md +429 -0
- package/skills/angular/table-state/references/external-atoms-and-app-hook.md +152 -0
- 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 +251 -0
- package/src/flex-render/renderer.ts +393 -0
- package/src/flex-render/view.ts +226 -0
- package/src/flexRender.ts +124 -0
- package/src/helpers/cell.ts +104 -0
- package/src/helpers/createTableHook.ts +499 -0
- package/src/helpers/flexRenderCell.ts +136 -0
- package/src/helpers/header.ts +99 -0
- package/src/helpers/table.ts +85 -0
- package/src/index.ts +23 -70
- package/src/injectTable.ts +212 -0
- package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
- package/src/reactivity.ts +105 -0
- package/static-functions/index.ts +1 -0
- package/static-functions/ng-package.json +6 -0
- package/dist/esm2022/flex-render.mjs +0 -148
- 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 -30
- 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/flex-render.ts +0 -184
- package/src/proxy.ts +0 -97
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { reflectComponentType } from '@angular/core'
|
|
2
|
+
import type {
|
|
3
|
+
Binding,
|
|
4
|
+
ComponentMirror,
|
|
5
|
+
Injector,
|
|
6
|
+
InputSignal,
|
|
7
|
+
OutputEmitterRef,
|
|
8
|
+
Type,
|
|
9
|
+
createComponent,
|
|
10
|
+
} from '@angular/core'
|
|
11
|
+
|
|
12
|
+
type CreateComponentOptions = Parameters<typeof createComponent>[1]
|
|
13
|
+
type CreateComponentBindings = CreateComponentOptions['bindings']
|
|
14
|
+
type CreateComponentDirectives = CreateComponentOptions['directives']
|
|
15
|
+
|
|
16
|
+
interface FlexRenderOptions<
|
|
17
|
+
TInputs extends Record<string, any>,
|
|
18
|
+
TOutputs extends Record<string, any>,
|
|
19
|
+
> {
|
|
20
|
+
/**
|
|
21
|
+
* Native Angular bindings applied at component creation time via `createComponent`.
|
|
22
|
+
* Use this option to set inputs, outputs, or two-way bindings at creation time.
|
|
23
|
+
* Shouldn't be used together with {@link FlexRenderOptions#inputs} or {@link FlexRenderOptions#outputs} option.
|
|
24
|
+
*
|
|
25
|
+
* Binding input/outputs at creation time: {@link https://angular.dev/guide/components/programmatic-rendering#binding-inputs-outputs-and-setting-host-directives-at-creation}
|
|
26
|
+
*
|
|
27
|
+
* Two-way binding: {@link https://angular.dev/api/core/twoWayBinding}
|
|
28
|
+
*
|
|
29
|
+
* Output binding: {@link https://angular.dev/api/core/outputBinding}
|
|
30
|
+
*
|
|
31
|
+
* Input binding: {@link https://angular.dev/api/core/inputBinding}
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import {flexRenderComponent} from '@tanstack/angular-table';
|
|
36
|
+
* flexRenderComponent(MyComponent, {
|
|
37
|
+
* bindings: [
|
|
38
|
+
* // Will update `value` input every time `mySignalValue` changes
|
|
39
|
+
* inputBinding('value', mySignalValue),
|
|
40
|
+
* // Set myProperty to 1 when the component is created
|
|
41
|
+
* inputBinding('myProperty', () => 1),
|
|
42
|
+
* // Callback called every time `valueChange` output emit
|
|
43
|
+
* outputBinding('valueChange', value => {
|
|
44
|
+
* console.log("my value changed to", value)
|
|
45
|
+
* }),
|
|
46
|
+
* // Two-way binding between `value` input and `valueChange` output
|
|
47
|
+
* // Useful while using `model` inputs.
|
|
48
|
+
* twoWayBinding('value', mySignal)
|
|
49
|
+
* ]
|
|
50
|
+
* })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
readonly bindings?: Array<Binding>
|
|
54
|
+
/**
|
|
55
|
+
* Directives to apply to the component at creation time.
|
|
56
|
+
*
|
|
57
|
+
* Binding directives at creation time: {@link https://angular.dev/guide/components/programmatic-rendering#binding-inputs-outputs-and-setting-host-directives-at-creation}
|
|
58
|
+
*
|
|
59
|
+
* Two-way binding: {@link https://angular.dev/api/core/twoWayBinding}
|
|
60
|
+
*
|
|
61
|
+
* Output binding: {@link https://angular.dev/api/core/outputBinding}
|
|
62
|
+
*
|
|
63
|
+
* Input binding: {@link https://angular.dev/api/core/inputBinding}
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import {flexRenderComponent} from '@tanstack/angular-table';
|
|
68
|
+
* flexRenderComponent(MyComponent, {
|
|
69
|
+
* bindings: [
|
|
70
|
+
* // ...
|
|
71
|
+
* ],
|
|
72
|
+
* directives: [
|
|
73
|
+
* DirectiveA,
|
|
74
|
+
* {
|
|
75
|
+
* type: DirectiveB,
|
|
76
|
+
* bindings: [
|
|
77
|
+
* inputBinding('value', mySignalValue),
|
|
78
|
+
* // ...
|
|
79
|
+
* ]
|
|
80
|
+
* }
|
|
81
|
+
* ]
|
|
82
|
+
* })
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
readonly directives?: CreateComponentDirectives
|
|
86
|
+
/**
|
|
87
|
+
* Component instance inputs.
|
|
88
|
+
*
|
|
89
|
+
* These values are assigned after the component has been created using
|
|
90
|
+
* [componentRef.setInput API](https://angular.dev/api/core/ComponentRef#setInput).
|
|
91
|
+
*
|
|
92
|
+
* Shouldn't be used together with {@link FlexRenderOptions#bindings} option
|
|
93
|
+
*/
|
|
94
|
+
readonly inputs?: TInputs
|
|
95
|
+
/**
|
|
96
|
+
* Component instance outputs.
|
|
97
|
+
*
|
|
98
|
+
* Outputs are wired imperatively after component creation using {@link OutputEmitterRef#subscribe}.
|
|
99
|
+
*
|
|
100
|
+
* Shouldn't be used together with {@link FlexRenderOptions#bindings} option
|
|
101
|
+
*/
|
|
102
|
+
readonly outputs?: TOutputs
|
|
103
|
+
/**
|
|
104
|
+
* Optional {@link Injector} that will be used when rendering the component
|
|
105
|
+
*/
|
|
106
|
+
readonly injector?: Injector
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type Inputs<T> = {
|
|
110
|
+
[K in keyof T as T[K] extends InputSignal<infer R>
|
|
111
|
+
? K
|
|
112
|
+
: never]?: T[K] extends InputSignal<infer R> ? R : never
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type Outputs<T> = {
|
|
116
|
+
[K in keyof T as T[K] extends OutputEmitterRef<infer R>
|
|
117
|
+
? K
|
|
118
|
+
: never]?: T[K] extends OutputEmitterRef<infer R>
|
|
119
|
+
? OutputEmitterRef<R>['emit']
|
|
120
|
+
: never
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Helper function to create a {@link FlexRenderComponent} instance, with better type-safety.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* import {flexRenderComponent} from '@tanstack/angular-table'
|
|
129
|
+
* import {inputBinding, outputBinding} from '@angular/core';
|
|
130
|
+
*
|
|
131
|
+
* const columns = [
|
|
132
|
+
* {
|
|
133
|
+
* cell: ({ row }) => {
|
|
134
|
+
* return flexRenderComponent(MyComponent, {
|
|
135
|
+
* inputs: { value: mySignalValue() },
|
|
136
|
+
* outputs: { valueChange: (val) => {} }
|
|
137
|
+
* // or using angular native createComponent#binding api
|
|
138
|
+
* bindings: [
|
|
139
|
+
* inputBinding('value', mySignalValue),
|
|
140
|
+
* outputBinding('valueChange', value => {
|
|
141
|
+
* console.log("my value changed to", value)
|
|
142
|
+
* })
|
|
143
|
+
* ]
|
|
144
|
+
* })
|
|
145
|
+
* },
|
|
146
|
+
* },
|
|
147
|
+
* ]
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function flexRenderComponent<TComponent = any>(
|
|
151
|
+
component: Type<TComponent>,
|
|
152
|
+
options?: FlexRenderOptions<Inputs<TComponent>, Outputs<TComponent>>,
|
|
153
|
+
): FlexRenderComponent<TComponent> {
|
|
154
|
+
const { inputs, injector, outputs, directives, bindings } = options ?? {}
|
|
155
|
+
return new FlexRenderComponentInstance(
|
|
156
|
+
component,
|
|
157
|
+
inputs,
|
|
158
|
+
injector,
|
|
159
|
+
outputs,
|
|
160
|
+
directives,
|
|
161
|
+
bindings,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Wrapper interface for a component that will be used as content for {@link FlexRenderDirective}.
|
|
167
|
+
* Can be created using {@link flexRenderComponent} helper.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
*
|
|
171
|
+
* ```ts
|
|
172
|
+
* import {flexRenderComponent} from '@tanstack/angular-table'
|
|
173
|
+
*
|
|
174
|
+
* // Usage in cell/header/footer definition
|
|
175
|
+
* const columns = [
|
|
176
|
+
* {
|
|
177
|
+
* cell: ({ row }) => {
|
|
178
|
+
* return flexRenderComponent(MyComponent, {
|
|
179
|
+
* inputs: { value: mySignalValue() },
|
|
180
|
+
* outputs: { valueChange: (val) => {} }
|
|
181
|
+
* // or using angular createComponent#bindings api
|
|
182
|
+
* bindings: [
|
|
183
|
+
* inputBinding('value', mySignalValue),
|
|
184
|
+
* outputBinding('valueChange', value => {
|
|
185
|
+
* console.log("my value changed to", value)
|
|
186
|
+
* })
|
|
187
|
+
* ]
|
|
188
|
+
* })
|
|
189
|
+
* },
|
|
190
|
+
* },
|
|
191
|
+
* ]
|
|
192
|
+
*
|
|
193
|
+
* import {input, output} from '@angular/core';
|
|
194
|
+
*
|
|
195
|
+
* @Component({
|
|
196
|
+
* selector: 'my-component',
|
|
197
|
+
* })
|
|
198
|
+
* class MyComponent {
|
|
199
|
+
* readonly value = input(0);
|
|
200
|
+
* readonly valueChange = output<number>();
|
|
201
|
+
* }
|
|
202
|
+
*
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export interface FlexRenderComponent<TComponent = any> {
|
|
206
|
+
/**
|
|
207
|
+
* The component type
|
|
208
|
+
*/
|
|
209
|
+
readonly component: Type<TComponent>
|
|
210
|
+
/**
|
|
211
|
+
* Reflected metadata about the component.
|
|
212
|
+
*/
|
|
213
|
+
readonly mirror: ComponentMirror<TComponent>
|
|
214
|
+
/**
|
|
215
|
+
* List of allowed input names.
|
|
216
|
+
*/
|
|
217
|
+
readonly allowedInputNames: Array<string>
|
|
218
|
+
/**
|
|
219
|
+
* List of allowed output names.
|
|
220
|
+
*/
|
|
221
|
+
readonly allowedOutputNames: Array<string>
|
|
222
|
+
/**
|
|
223
|
+
* Component instance outputs. Subscribed via {@link OutputEmitterRef#subscribe}
|
|
224
|
+
*
|
|
225
|
+
* @see {@link FlexRenderOptions#outputs}
|
|
226
|
+
*/
|
|
227
|
+
readonly outputs?: Outputs<TComponent>
|
|
228
|
+
/**
|
|
229
|
+
* Component instance inputs. Set via [componentRef.setInput API](https://angular.dev/api/core/ComponentRef#setInput))
|
|
230
|
+
*
|
|
231
|
+
* @see {@link FlexRenderOptions#inputs}
|
|
232
|
+
*/
|
|
233
|
+
readonly inputs?: Inputs<TComponent>
|
|
234
|
+
/**
|
|
235
|
+
* Optional {@link Injector} that will be used when rendering the component.
|
|
236
|
+
*
|
|
237
|
+
* @see {@link FlexRenderOptions#injector}
|
|
238
|
+
*/
|
|
239
|
+
readonly injector?: Injector
|
|
240
|
+
/**
|
|
241
|
+
* Bindings to apply to the root component
|
|
242
|
+
*
|
|
243
|
+
* @see {@link FlexRenderOptions#bindings}
|
|
244
|
+
*/
|
|
245
|
+
bindings?: CreateComponentBindings
|
|
246
|
+
/**
|
|
247
|
+
* Directives that should be applied to the component.
|
|
248
|
+
*
|
|
249
|
+
* @see {FlexRenderOptions#directives}
|
|
250
|
+
*/
|
|
251
|
+
directives?: CreateComponentDirectives
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Wrapper class for a component that will be used as content for {@link FlexRenderDirective}
|
|
256
|
+
*
|
|
257
|
+
* Prefer {@link flexRenderComponent} helper for better type-safety
|
|
258
|
+
*/
|
|
259
|
+
export class FlexRenderComponentInstance<
|
|
260
|
+
TComponent = any,
|
|
261
|
+
> implements FlexRenderComponent<TComponent> {
|
|
262
|
+
readonly mirror: ComponentMirror<TComponent>
|
|
263
|
+
readonly allowedInputNames: Array<string> = []
|
|
264
|
+
readonly allowedOutputNames: Array<string> = []
|
|
265
|
+
|
|
266
|
+
constructor(
|
|
267
|
+
readonly component: Type<TComponent>,
|
|
268
|
+
readonly inputs?: Inputs<TComponent>,
|
|
269
|
+
readonly injector?: Injector,
|
|
270
|
+
readonly outputs?: Outputs<TComponent>,
|
|
271
|
+
readonly directives?: CreateComponentDirectives,
|
|
272
|
+
readonly bindings?: CreateComponentBindings,
|
|
273
|
+
) {
|
|
274
|
+
const mirror = reflectComponentType(component)
|
|
275
|
+
if (!mirror) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
`[@tanstack-table/angular] The provided symbol is not a component`,
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
this.mirror = mirror
|
|
281
|
+
for (const input of this.mirror.inputs) {
|
|
282
|
+
this.allowedInputNames.push(input.propName)
|
|
283
|
+
}
|
|
284
|
+
for (const output of this.mirror.outputs) {
|
|
285
|
+
this.allowedOutputNames.push(output.propName)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectorRef,
|
|
3
|
+
ComponentRef,
|
|
4
|
+
Injectable,
|
|
5
|
+
Injector,
|
|
6
|
+
KeyValueDiffer,
|
|
7
|
+
KeyValueDiffers,
|
|
8
|
+
OutputEmitterRef,
|
|
9
|
+
OutputRefSubscription,
|
|
10
|
+
ViewContainerRef,
|
|
11
|
+
} from '@angular/core'
|
|
12
|
+
import { FlexRenderComponent } from './flexRenderComponent'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates and manages Angular component instances used by flex-rendered table
|
|
16
|
+
* content.
|
|
17
|
+
*/
|
|
18
|
+
@Injectable()
|
|
19
|
+
export class FlexRenderComponentFactory {
|
|
20
|
+
readonly #viewContainerRef: ViewContainerRef
|
|
21
|
+
|
|
22
|
+
constructor(viewContainerRef: ViewContainerRef) {
|
|
23
|
+
this.#viewContainerRef = viewContainerRef
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
createComponent<T>(
|
|
27
|
+
flexRenderComponent: FlexRenderComponent<T>,
|
|
28
|
+
componentInjector: Injector,
|
|
29
|
+
): FlexRenderComponentRef<T> {
|
|
30
|
+
const componentRef = this.#viewContainerRef.createComponent(
|
|
31
|
+
flexRenderComponent.component,
|
|
32
|
+
{
|
|
33
|
+
injector: componentInjector,
|
|
34
|
+
directives: flexRenderComponent.directives,
|
|
35
|
+
bindings: flexRenderComponent.bindings ?? [],
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
const view = new FlexRenderComponentRef(
|
|
39
|
+
componentRef,
|
|
40
|
+
flexRenderComponent,
|
|
41
|
+
componentInjector,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const { inputs, outputs } = flexRenderComponent
|
|
45
|
+
|
|
46
|
+
if (inputs) view.setInputs(inputs)
|
|
47
|
+
if (outputs) view.setOutputs(outputs)
|
|
48
|
+
|
|
49
|
+
return view
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Runtime wrapper around an Angular component rendered by `FlexRenderDirective`.
|
|
55
|
+
*
|
|
56
|
+
* It diffs inputs and outputs across table updates so component renderers can
|
|
57
|
+
* be reused instead of recreated on every cell/header render.
|
|
58
|
+
*/
|
|
59
|
+
export class FlexRenderComponentRef<T> {
|
|
60
|
+
readonly #keyValueDiffersFactory: KeyValueDiffers
|
|
61
|
+
#componentData: FlexRenderComponent<T>
|
|
62
|
+
#inputValueDiffer: KeyValueDiffer<string, unknown>
|
|
63
|
+
|
|
64
|
+
readonly #outputRegistry: FlexRenderComponentOutputManager
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
readonly componentRef: ComponentRef<T>,
|
|
68
|
+
componentData: FlexRenderComponent<T>,
|
|
69
|
+
readonly componentInjector: Injector,
|
|
70
|
+
) {
|
|
71
|
+
this.#componentData = componentData
|
|
72
|
+
this.#keyValueDiffersFactory = componentInjector.get(KeyValueDiffers)
|
|
73
|
+
|
|
74
|
+
this.#outputRegistry = new FlexRenderComponentOutputManager(
|
|
75
|
+
this.#keyValueDiffersFactory,
|
|
76
|
+
this.outputs,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
this.#inputValueDiffer = this.#keyValueDiffersFactory
|
|
80
|
+
.find(this.inputs)
|
|
81
|
+
.create()
|
|
82
|
+
this.#inputValueDiffer.diff(this.inputs)
|
|
83
|
+
|
|
84
|
+
this.componentRef.onDestroy(() => this.#outputRegistry.unsubscribeAll())
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get component() {
|
|
88
|
+
return this.#componentData.component
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get inputs() {
|
|
92
|
+
return this.#componentData.inputs ?? {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get outputs() {
|
|
96
|
+
return this.#componentData.outputs ?? {}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get component input and output diff by the given item
|
|
101
|
+
*/
|
|
102
|
+
diff(item: FlexRenderComponent<T>) {
|
|
103
|
+
return {
|
|
104
|
+
inputDiff: this.#inputValueDiffer.diff(item.inputs ?? {}),
|
|
105
|
+
outputDiff: this.#outputRegistry.diff(item.outputs ?? {}),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
*
|
|
110
|
+
* @param compare Whether the current ref component instance is the same as the given one
|
|
111
|
+
*/
|
|
112
|
+
eqType(compare: FlexRenderComponent<T>): boolean {
|
|
113
|
+
return compare.component === this.component
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tries to update current component refs input by the new given content component.
|
|
118
|
+
*/
|
|
119
|
+
update(content: FlexRenderComponent<T>) {
|
|
120
|
+
const eq = this.eqType(content)
|
|
121
|
+
if (!eq) return
|
|
122
|
+
const { inputDiff, outputDiff } = this.diff(content)
|
|
123
|
+
if (inputDiff) {
|
|
124
|
+
inputDiff.forEachAddedItem((item) =>
|
|
125
|
+
this.setInput(item.key, item.currentValue),
|
|
126
|
+
)
|
|
127
|
+
inputDiff.forEachChangedItem((item) =>
|
|
128
|
+
this.setInput(item.key, item.currentValue),
|
|
129
|
+
)
|
|
130
|
+
inputDiff.forEachRemovedItem((item) => this.setInput(item.key, undefined))
|
|
131
|
+
}
|
|
132
|
+
if (outputDiff) {
|
|
133
|
+
outputDiff.forEachAddedItem((item) => {
|
|
134
|
+
this.setOutput(item.key, item.currentValue)
|
|
135
|
+
})
|
|
136
|
+
outputDiff.forEachChangedItem((item) => {
|
|
137
|
+
if (item.currentValue) {
|
|
138
|
+
this.#outputRegistry.setListener(item.key, item.currentValue)
|
|
139
|
+
} else {
|
|
140
|
+
this.#outputRegistry.unsubscribe(item.key)
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
outputDiff.forEachRemovedItem((item) => {
|
|
144
|
+
this.#outputRegistry.unsubscribe(item.key)
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.#componentData = content
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
markAsDirty(): void {
|
|
152
|
+
this.componentRef.injector.get(ChangeDetectorRef).markForCheck()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setInputs(inputs: Record<string, unknown>) {
|
|
156
|
+
for (const prop in inputs) {
|
|
157
|
+
this.setInput(prop, inputs[prop])
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setInput(key: string, value: unknown) {
|
|
162
|
+
if (this.#componentData.allowedInputNames.includes(key)) {
|
|
163
|
+
this.componentRef.setInput(key, value)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setOutputs(
|
|
168
|
+
outputs: Record<
|
|
169
|
+
string,
|
|
170
|
+
OutputEmitterRef<unknown>['emit'] | null | undefined
|
|
171
|
+
>,
|
|
172
|
+
) {
|
|
173
|
+
this.#outputRegistry.unsubscribeAll()
|
|
174
|
+
for (const prop in outputs) {
|
|
175
|
+
this.setOutput(prop, outputs[prop])
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
setOutput(
|
|
180
|
+
outputName: string,
|
|
181
|
+
emit: OutputEmitterRef<unknown>['emit'] | undefined | null,
|
|
182
|
+
): void {
|
|
183
|
+
if (!this.#componentData.allowedOutputNames.includes(outputName)) return
|
|
184
|
+
if (!emit) {
|
|
185
|
+
this.#outputRegistry.unsubscribe(outputName)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const hasListener = this.#outputRegistry.hasListener(outputName)
|
|
190
|
+
this.#outputRegistry.setListener(outputName, emit)
|
|
191
|
+
|
|
192
|
+
if (hasListener) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const instance = this.componentRef.instance
|
|
197
|
+
const output = instance[outputName as keyof typeof instance]
|
|
198
|
+
if (output && output instanceof OutputEmitterRef) {
|
|
199
|
+
output.subscribe((value) => {
|
|
200
|
+
this.#outputRegistry.getListener(outputName)?.(value)
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
class FlexRenderComponentOutputManager {
|
|
207
|
+
readonly #outputSubscribers: Record<string, OutputRefSubscription> = {}
|
|
208
|
+
readonly #outputListeners: Record<string, (...args: Array<any>) => void> = {}
|
|
209
|
+
|
|
210
|
+
readonly #valueDiffer: KeyValueDiffer<
|
|
211
|
+
string,
|
|
212
|
+
undefined | null | OutputEmitterRef<unknown>['emit']
|
|
213
|
+
>
|
|
214
|
+
|
|
215
|
+
constructor(keyValueDiffers: KeyValueDiffers, initialOutputs: any) {
|
|
216
|
+
this.#valueDiffer = keyValueDiffers.find(initialOutputs).create()
|
|
217
|
+
if (initialOutputs) {
|
|
218
|
+
this.#valueDiffer.diff(initialOutputs)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
hasListener(outputName: string) {
|
|
223
|
+
return outputName in this.#outputListeners
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setListener(outputName: string, callback: (...args: Array<any>) => void) {
|
|
227
|
+
this.#outputListeners[outputName] = callback
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
getListener(outputName: string) {
|
|
231
|
+
return this.#outputListeners[outputName]
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
unsubscribeAll(): void {
|
|
235
|
+
for (const prop in this.#outputSubscribers) {
|
|
236
|
+
this.unsubscribe(prop)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
unsubscribe(outputName: string) {
|
|
241
|
+
if (outputName in this.#outputSubscribers) {
|
|
242
|
+
this.#outputSubscribers[outputName]?.unsubscribe()
|
|
243
|
+
delete this.#outputSubscribers[outputName]
|
|
244
|
+
delete this.#outputListeners[outputName]
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
diff(outputs: Record<string, OutputEmitterRef<unknown>['emit'] | undefined>) {
|
|
249
|
+
return this.#valueDiffer.diff(outputs)
|
|
250
|
+
}
|
|
251
|
+
}
|