11import {
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'
2018import { FlexRenderComponentProps } from './flex-render/context'
2119import { 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'
3334import type { InputSignal } from '@angular/core'
3435import type { FlexRenderTypedContent } from './flex-render/view'
3536import 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