Skip to content

Commit a488757

Browse files
committed
refactor flexRender to work with signals
1 parent a024ecc commit a488757

File tree

3 files changed

+93
-96
lines changed

3 files changed

+93
-96
lines changed

packages/angular-table/src/angularReactivityFeature.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export interface AngularReactivityFlags {
3030
}
3131

3232
interface TableOptions_AngularReactivity {
33-
enableExperimentalReactivity?: boolean
3433
reactivity?: Partial<AngularReactivityFlags>
3534
}
3635

packages/angular-table/src/context/flex-render.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Directive, effect, inject, input, untracked } from '@angular/core'
1+
import { Directive, effect, inject, input } from '@angular/core'
22
import {
33
Cell,
44
CellData,
@@ -45,17 +45,6 @@ export class CellFlexRender<
4545
if (cell) {
4646
content.set(cell.column.columnDef.cell)
4747
props.set(cell.getContext())
48-
// TODO: fix
49-
untracked(() =>
50-
this.#flexRender.ngOnChanges({
51-
inputContent: {
52-
currentValue: cell.column.columnDef.cell,
53-
},
54-
inputProps: {
55-
currentValue: cell.getContext(),
56-
},
57-
} as any),
58-
)
5948
staticProviders.set([
6049
{ provide: TableContextToken, useValue: () => cell.table },
6150
{ provide: CellContextToken, useValue: () => cell },

packages/angular-table/src/flex-render.ts

Lines changed: 92 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import {
2-
ChangeDetectorRef,
2+
DestroyRef,
33
Directive,
4-
DoCheck,
54
EffectRef,
65
Injector,
7-
OnChanges,
86
Provider,
9-
SimpleChanges,
107
TemplateRef,
118
Type,
129
ViewContainerRef,
@@ -16,6 +13,7 @@ import {
1613
input,
1714
linkedSignal,
1815
runInInjectionContext,
16+
untracked,
1917
} from '@angular/core'
2018
import { FlexRenderComponentProps } from './flex-render/context'
2119
import { FlexRenderFlags } from './flex-render/flags'
@@ -30,6 +28,9 @@ import {
3028
FlexRenderView,
3129
mapToFlexRenderTypedContent,
3230
} from './flex-render/view'
31+
import { CellContextToken } from './context/cell'
32+
import { HeaderContextToken } from './context/header'
33+
import { TableContextToken } from './context/table'
3334
import type { InputSignal } from '@angular/core'
3435
import type { FlexRenderTypedContent } from './flex-render/view'
3536
import type {
@@ -56,6 +57,13 @@ export type FlexRenderContent<TProps extends NonNullable<unknown>> =
5657
| Record<any, any>
5758
| undefined
5859

60+
export type FlexRenderInputContent<TProps extends NonNullable<unknown>> =
61+
| number
62+
| string
63+
| ((props: TProps) => FlexRenderContent<TProps>)
64+
| null
65+
| undefined
66+
5967
@Directive({
6068
selector: 'ng-template[flexRender]',
6169
standalone: true,
@@ -68,35 +76,24 @@ export class FlexRender<
6876
| NonNullable<unknown>
6977
| CellContext<TFeatures, TRowData, TValue>
7078
| HeaderContext<TFeatures, TRowData, TValue>,
71-
>
72-
implements DoCheck, OnChanges
73-
{
79+
> {
7480
readonly #flexRenderComponentFactory = new FlexRenderComponentFactory(
7581
inject(ViewContainerRef),
7682
)
77-
readonly #changeDetectorRef = inject(ChangeDetectorRef)
78-
79-
readonly inputContent: InputSignal<
80-
| number
81-
| string
82-
| ((props: TProps) => FlexRenderContent<TProps>)
83-
| null
84-
| undefined
85-
> = input(undefined as any, {
86-
alias: 'flexRender',
87-
})
83+
84+
readonly inputContent: InputSignal<FlexRenderInputContent<TProps>> = input(
85+
undefined as FlexRenderInputContent<TProps>,
86+
{
87+
alias: 'flexRender',
88+
},
89+
)
8890
readonly content = linkedSignal(() => this.inputContent())
8991

9092
readonly inputProps = input<TProps>({} as TProps, {
9193
alias: 'flexRenderProps',
9294
})
9395
readonly props = linkedSignal(() => this.inputProps())
9496

95-
readonly inputNotifier = input<'doCheck' | 'tableChange'>('doCheck', {
96-
alias: 'flexRenderNotifier',
97-
})
98-
readonly notifier = linkedSignal(() => this.inputNotifier())
99-
10097
readonly #injector = inject(Injector)
10198
readonly inputInjector = input(this.#injector, {
10299
alias: 'flexRenderInjector',
@@ -131,34 +128,40 @@ export class FlexRender<
131128
return mapToFlexRenderTypedContent(latestContent)
132129
})
133130

134-
ngOnChanges(
135-
changes: SimpleChanges<FlexRender<TFeatures, TRowData, TValue, TProps>>,
136-
) {
137-
if (changes.inputProps) {
138-
const props = changes.inputProps.currentValue
139-
this.table = 'table' in props ? props.table : null
140-
this.renderFlags |= FlexRenderFlags.PropsReferenceChanged
141-
this.bindTableDirtyCheck()
142-
}
143-
if (changes.inputContent) {
144-
this.renderFlags |=
145-
FlexRenderFlags.ContentChanged | FlexRenderFlags.ViewFirstRender
146-
this.update()
147-
}
148-
}
131+
constructor() {
132+
let previousContent: FlexRenderInputContent<TProps>
133+
let previousProps: TProps
149134

150-
ngDoCheck(): void {
151-
if (this.renderFlags & FlexRenderFlags.ViewFirstRender) {
152-
// On the initial render, the view is created during the `ngOnChanges` hook.
153-
// Since `ngDoCheck` is called immediately afterward, there's no need to check for changes in this phase.
154-
this.renderFlags &= ~FlexRenderFlags.ViewFirstRender
155-
return
156-
}
135+
effect(() => {
136+
const props = this.props()
137+
const content = this.content()
157138

158-
if (this.notifier() === 'doCheck') {
159-
this.renderFlags |= FlexRenderFlags.DirtyCheck
160-
this.doCheck()
161-
}
139+
untracked(() => {
140+
if (this.renderFlags & FlexRenderFlags.ViewFirstRender) {
141+
} else {
142+
if (previousContent !== content) {
143+
this.renderFlags |= FlexRenderFlags.ContentChanged
144+
}
145+
if (previousProps !== props) {
146+
this.renderFlags |= FlexRenderFlags.PropsReferenceChanged
147+
}
148+
}
149+
150+
this.update()
151+
})
152+
153+
previousContent = content
154+
previousProps = props
155+
})
156+
157+
inject(DestroyRef).onDestroy(() => {
158+
if (this.#currentEffectRef) {
159+
this.#currentEffectRef.destroy()
160+
this.#currentEffectRef = null
161+
this.renderView?.unmount()
162+
this.renderView = null
163+
}
164+
})
162165
}
163166

164167
private doCheck() {
@@ -175,35 +178,15 @@ export class FlexRender<
175178
this.update()
176179
}
177180

178-
#tableChangeEffect: EffectRef | null = null
179-
180-
private bindTableDirtyCheck() {
181-
this.#tableChangeEffect?.destroy()
182-
this.#tableChangeEffect = null
183-
let firstCheck = !!(this.renderFlags & FlexRenderFlags.ViewFirstRender)
184-
if (this.table && this.notifier() === 'tableChange') {
185-
const table = this.table
186-
this.#tableChangeEffect = effect(
187-
() => {
188-
table.get()
189-
if (firstCheck) {
190-
firstCheck = false
191-
return
192-
}
193-
this.renderFlags |= FlexRenderFlags.DirtyCheck
194-
this.doCheck()
195-
},
196-
{ injector: this.#injector },
197-
)
198-
}
199-
}
200-
201181
update() {
202182
if (
203183
this.renderFlags &
204184
(FlexRenderFlags.ContentChanged | FlexRenderFlags.ViewFirstRender)
205185
) {
206186
this.render()
187+
if (FlexRenderFlags.ViewFirstRender & this.renderFlags) {
188+
this.renderFlags &= ~FlexRenderFlags.ViewFirstRender
189+
}
207190
return
208191
}
209192
if (this.renderFlags & FlexRenderFlags.PropsReferenceChanged) {
@@ -259,9 +242,7 @@ export class FlexRender<
259242
return
260243
}
261244
this.renderFlags |= FlexRenderFlags.DirtySignal
262-
// This will mark the view as changed,
263-
// so we'll try to check for updates into ngDoCheck
264-
this.#changeDetectorRef.markForCheck()
245+
this.doCheck()
265246
},
266247
{ injector: this.viewContainerRef.injector },
267248
)
@@ -313,11 +294,15 @@ export class FlexRender<
313294
template: Extract<FlexRenderTypedContent, { kind: 'templateRef' }>,
314295
): FlexRenderTemplateView {
315296
const latestContext = () => this.props()
316-
const view = this.viewContainerRef.createEmbeddedView(template.content, {
317-
get $implicit() {
318-
return latestContext()
297+
const view = this.viewContainerRef.createEmbeddedView(
298+
template.content,
299+
{
300+
get $implicit() {
301+
return latestContext()
302+
},
319303
},
320-
})
304+
{ injector: this.#getInjector() },
305+
)
321306
return new FlexRenderTemplateView(template, view)
322307
}
323308

@@ -328,7 +313,7 @@ export class FlexRender<
328313
>,
329314
): FlexRenderComponentView {
330315
const { injector } = flexRenderComponent.content
331-
const componentInjector = this.#getComponentInjector(injector)
316+
const componentInjector = this.#getInjector(injector)
332317
const view = this.#flexRenderComponentFactory.createComponent(
333318
flexRenderComponent.content,
334319
componentInjector,
@@ -341,7 +326,7 @@ export class FlexRender<
341326
): FlexRenderComponentView {
342327
const instance = flexRenderComponent(component.content, {
343328
inputs: this.props(),
344-
injector: this.#getComponentInjector(this.injector()),
329+
injector: this.#getInjector(this.injector()),
345330
})
346331
const view = this.#flexRenderComponentFactory.createComponent(
347332
instance,
@@ -350,15 +335,39 @@ export class FlexRender<
350335
return new FlexRenderComponentView(component, view)
351336
}
352337

353-
#getComponentInjector(parentInjector?: Injector) {
338+
#getInjector(parentInjector?: Injector) {
354339
const getContext = () => this.props()
355340
const proxy = new Proxy(this.props(), {
356341
get: (_, key) => getContext()[key as keyof typeof _],
357342
})
343+
344+
const staticProviders = [...this.staticProviders()]
345+
const context = getContext()
346+
if ('cell' in context) {
347+
staticProviders.push({
348+
provide: CellContextToken,
349+
useValue: () => context.cell,
350+
})
351+
staticProviders.push({
352+
provide: TableContextToken,
353+
useValue: () => context.table,
354+
})
355+
}
356+
if ('header' in context) {
357+
staticProviders.push({
358+
provide: HeaderContextToken,
359+
useValue: () => context.header,
360+
})
361+
staticProviders.push({
362+
provide: TableContextToken,
363+
useValue: () => context.table,
364+
})
365+
}
366+
358367
return Injector.create({
359368
parent: parentInjector ?? this.injector(),
360369
providers: [
361-
...this.staticProviders(),
370+
...staticProviders,
362371
{ provide: FlexRenderComponentProps, useValue: proxy },
363372
],
364373
})

0 commit comments

Comments
 (0)