11import {
2- computed ,
2+ ChangeDetectorRef ,
33 Directive ,
44 DoCheck ,
55 effect ,
6+ type EffectRef ,
67 Inject ,
78 inject ,
89 Injector ,
@@ -12,7 +13,6 @@ import {
1213 SimpleChanges ,
1314 TemplateRef ,
1415 Type ,
15- untracked ,
1616 ViewContainerRef ,
1717} from '@angular/core'
1818import { FlexRenderComponentProps } from './flex-render/context'
@@ -26,6 +26,7 @@ import {
2626 FlexRenderView ,
2727 mapToFlexRenderTypedContent ,
2828} from './flex-render/view'
29+ import { memo } from '@tanstack/table-core'
2930
3031export {
3132 injectFlexRenderContext ,
@@ -39,7 +40,7 @@ export type FlexRenderContent<TProps extends NonNullable<unknown>> =
3940 | FlexRenderComponent < TProps >
4041 | TemplateRef < { $implicit : TProps } >
4142 | null
42- | Record < string , any >
43+ | Record < any , any >
4344 | undefined
4445
4546@Directive ( {
@@ -50,6 +51,9 @@ export type FlexRenderContent<TProps extends NonNullable<unknown>> =
5051export class FlexRenderDirective < TProps extends NonNullable < unknown > >
5152 implements OnChanges , DoCheck
5253{
54+ readonly #flexRenderComponentFactory = inject ( FlexRenderComponentFactory )
55+ readonly #changeDetectorRef = inject ( ChangeDetectorRef )
56+
5357 @Input ( { required : true , alias : 'flexRender' } )
5458 content :
5559 | number
@@ -64,10 +68,24 @@ export class FlexRenderDirective<TProps extends NonNullable<unknown>>
6468 @Input ( { required : false , alias : 'flexRenderInjector' } )
6569 injector : Injector = inject ( Injector )
6670
67- readonly #flexRenderComponentFactory = inject ( FlexRenderComponentFactory )
68- renderFlags = FlexRenderFlags . Creation
71+ renderFlags = FlexRenderFlags . ViewFirstRender
6972 renderView : FlexRenderView < any > | null = null
7073
74+ readonly #latestContent = ( ) => {
75+ const { content, props } = this
76+ return typeof content !== 'function'
77+ ? content
78+ : runInInjectionContext ( this . injector , ( ) => content ( props ) )
79+ }
80+
81+ #getContentValue = memo (
82+ ( ) => [ this . #latestContent( ) , this . props , this . content ] ,
83+ latestContent => {
84+ return mapToFlexRenderTypedContent ( latestContent )
85+ } ,
86+ { key : 'flexRenderContentValue' , debug : ( ) => false }
87+ )
88+
7189 constructor (
7290 @Inject ( ViewContainerRef )
7391 private readonly viewContainerRef : ViewContainerRef ,
@@ -80,120 +98,145 @@ export class FlexRenderDirective<TProps extends NonNullable<unknown>>
8098 this . renderFlags |= FlexRenderFlags . PropsReferenceChanged
8199 }
82100 if ( changes [ 'content' ] ) {
83- this . renderFlags |= FlexRenderFlags . ContentChanged
84- this . checkView ( )
101+ this . renderFlags |=
102+ FlexRenderFlags . ContentChanged | FlexRenderFlags . ViewFirstRender
103+ this . update ( )
85104 }
86105 }
87106
88107 ngDoCheck ( ) : void {
89- if ( this . renderFlags & FlexRenderFlags . Creation ) {
108+ if ( this . renderFlags & FlexRenderFlags . ViewFirstRender ) {
90109 // On the initial render, the view is created during the `ngOnChanges` hook.
91110 // Since `ngDoCheck` is called immediately afterward, there's no need to check for changes in this phase.
92- this . renderFlags &= ~ FlexRenderFlags . Creation
111+ this . renderFlags &= ~ FlexRenderFlags . ViewFirstRender
93112 return
94113 }
95114
96- // TODO: Optimization for V9?. We could check for signal changes when
97- // we have a way to detect here whether the table state changes
115+ console . log ( 'go into do check' )
116+
117+ // TODO: Optimization for V9 / future updates?. We could check for dirty signal changes when
118+ // we are able to detect whether the table state changes here
98119 // const isChanged =
99120 // this.renderFlags &
100121 // (FlexRenderFlags.DirtySignal | FlexRenderFlags.PropsReferenceChanged)
101122 // if (!isChanged) {
102123 // return
103124 // }
125+ // if (this.renderFlags & FlexRenderFlags.DirtySignal) {
126+ // this.renderFlags &= ~FlexRenderFlags.DirtySignal
127+ // return
128+ // }
104129
105- const contentToRender = untracked ( ( ) => this . #getContentValue( this . props ) )
130+ this . renderFlags |= FlexRenderFlags . DirtyCheck
131+ this . checkViewChanges ( )
132+ }
106133
107- if ( contentToRender . kind === 'null' || ! this . renderView ) {
108- this . renderFlags |= FlexRenderFlags . Creation
134+ checkViewChanges ( ) : void {
135+ const latestContent = this . #getContentValue( )
136+ if ( latestContent . kind === 'null' || ! this . renderView ) {
137+ this . renderFlags |= FlexRenderFlags . ContentChanged
109138 } else {
110- this . renderView . setContent ( contentToRender . content )
111- this . renderFlags |= FlexRenderFlags . DirtyCheck
112-
113- const previousContentInfo = this . renderView . previousContent
114- if ( contentToRender . kind !== previousContentInfo . kind ) {
139+ this . renderView . content = latestContent
140+ const { kind : previousKind } = this . renderView . previousContent
141+ if ( latestContent . kind !== previousKind ) {
115142 this . renderFlags |= FlexRenderFlags . ContentChanged
116143 }
117- this . renderFlags &= ~ FlexRenderFlags . Pristine
118144 }
119-
120- this . checkView ( )
145+ this . update ( )
121146 }
122147
123- checkView ( ) {
148+ update ( ) {
124149 if (
125150 this . renderFlags &
126- ( FlexRenderFlags . ContentChanged | FlexRenderFlags . Creation )
151+ ( FlexRenderFlags . ContentChanged | FlexRenderFlags . ViewFirstRender )
127152 ) {
128153 this . render ( )
129154 return
130155 }
131-
132156 if ( this . renderFlags & FlexRenderFlags . PropsReferenceChanged ) {
133157 if ( this . renderView ) this . renderView . updateProps ( this . props )
134158 this . renderFlags &= ~ FlexRenderFlags . PropsReferenceChanged
135159 }
136-
137- if ( this . renderFlags & FlexRenderFlags . DirtyCheck ) {
160+ if (
161+ this . renderFlags &
162+ ( FlexRenderFlags . DirtyCheck | FlexRenderFlags . DirtySignal )
163+ ) {
138164 if ( this . renderView ) this . renderView . dirtyCheck ( )
139165 this . renderFlags &= ~ (
140166 FlexRenderFlags . DirtyCheck | FlexRenderFlags . DirtySignal
141167 )
142168 }
143169 }
144170
171+ #currentEffectRef: EffectRef | null = null
172+
145173 render ( ) {
174+ if ( this . #shouldRecreateEntireView( ) && this . #currentEffectRef) {
175+ this . #currentEffectRef. destroy ( )
176+ this . #currentEffectRef = null
177+ this . renderFlags &= ~ FlexRenderFlags . RenderEffectChecked
178+ }
179+
146180 this . viewContainerRef . clear ( )
147- const resolvedContent = this . #getContentValue( this . props )
181+ this . renderFlags =
182+ FlexRenderFlags . Pristine |
183+ ( this . renderFlags & FlexRenderFlags . ViewFirstRender ) |
184+ ( this . renderFlags & FlexRenderFlags . RenderEffectChecked )
148185
186+ const resolvedContent = this . #getContentValue( )
149187 if ( resolvedContent . kind === 'null' ) {
150188 this . renderView = null
151- return
189+ } else {
190+ this . renderView = this . #renderViewByContent( resolvedContent )
152191 }
153192
154- this . renderView = this . #renderViewByContent( resolvedContent )
155-
156- if ( typeof this . content === 'function' ) {
157- let firstRender = true
158- const effectRef = effect (
193+ // If the content is a function `content(props)`, we initialize an effect
194+ // in order to react to changes if the given definition use signals.
195+ if ( ! this . #currentEffectRef && typeof this . content === 'function' ) {
196+ this . #currentEffectRef = effect (
159197 ( ) => {
160- resolvedContent . computedContent ( )
161- if ( firstRender ) {
162- firstRender = true
198+ this . #latestContent ( )
199+ if ( ! ( this . renderFlags & FlexRenderFlags . RenderEffectChecked ) ) {
200+ this . renderFlags |= FlexRenderFlags . RenderEffectChecked
163201 return
164202 }
165203 this . renderFlags |= FlexRenderFlags . DirtySignal
204+ // This will mark the view as changed,
205+ // so we'll try to check for updates into ngDoCheck
206+ this . #changeDetectorRef. markForCheck ( )
166207 } ,
167- { injector : this . injector }
208+ { injector : this . viewContainerRef . injector }
168209 )
169- if ( this . renderView ) {
170- this . renderView . onDestroy ( ( ) => {
171- effectRef . destroy ( )
172- } )
173- }
174210 }
211+ }
175212
176- this . renderFlags |= FlexRenderFlags . Pristine
177- this . renderFlags &= ~ FlexRenderFlags . ContentChanged
213+ #shouldRecreateEntireView( ) {
214+ return (
215+ this . renderFlags &
216+ FlexRenderFlags . ContentChanged &
217+ FlexRenderFlags . ViewFirstRender
218+ )
178219 }
179220
180221 #renderViewByContent(
181222 content : FlexRenderTypedContent
182223 ) : FlexRenderView < any > | null {
183224 if ( content . kind === 'primitive' ) {
184- return this . #renderStringContent( )
225+ return this . #renderStringContent( content )
185226 } else if ( content . kind === 'templateRef' ) {
186- return this . #renderTemplateRefContent( content . content )
227+ return this . #renderTemplateRefContent( content )
187228 } else if ( content . kind === 'flexRenderComponent' ) {
188- return this . #renderComponent( content . content )
229+ return this . #renderComponent( content )
189230 } else if ( content . kind === 'component' ) {
190- return this . #renderCustomComponent( content . content )
231+ return this . #renderCustomComponent( content )
191232 } else {
192233 return null
193234 }
194235 }
195236
196- #renderStringContent( ) : FlexRenderTemplateView {
237+ #renderStringContent(
238+ template : Extract < FlexRenderTypedContent , { kind : 'primitive' } >
239+ ) : FlexRenderTemplateView {
197240 const context = ( ) => {
198241 return typeof this . content === 'string' ||
199242 typeof this . content === 'number'
@@ -205,23 +248,28 @@ export class FlexRenderDirective<TProps extends NonNullable<unknown>>
205248 return context ( )
206249 } ,
207250 } )
208- return new FlexRenderTemplateView ( context ( ) , ref )
251+ return new FlexRenderTemplateView ( template , ref )
209252 }
210253
211- #renderTemplateRefContent( content : TemplateRef < any > ) : FlexRenderTemplateView {
254+ #renderTemplateRefContent(
255+ template : Extract < FlexRenderTypedContent , { kind : 'templateRef' } >
256+ ) : FlexRenderTemplateView {
212257 const latestContext = ( ) => this . props
213- const view = this . viewContainerRef . createEmbeddedView ( content , {
258+ const view = this . viewContainerRef . createEmbeddedView ( template . content , {
214259 get $implicit ( ) {
215260 return latestContext ( )
216261 } ,
217262 } )
218- return new FlexRenderTemplateView ( content , view )
263+ return new FlexRenderTemplateView ( template , view )
219264 }
220265
221266 #renderComponent(
222- flexRenderComponent : FlexRenderComponent
267+ flexRenderComponent : Extract <
268+ FlexRenderTypedContent ,
269+ { kind : 'flexRenderComponent' }
270+ >
223271 ) : FlexRenderComponentView {
224- const { inputs, injector } = flexRenderComponent
272+ const { inputs, injector } = flexRenderComponent . content
225273
226274 const getContext = ( ) => this . props
227275 const proxy = new Proxy ( this . props , {
@@ -232,33 +280,35 @@ export class FlexRenderDirective<TProps extends NonNullable<unknown>>
232280 providers : [ { provide : FlexRenderComponentProps , useValue : proxy } ] ,
233281 } )
234282 const view = this . #flexRenderComponentFactory. createComponent (
235- flexRenderComponent ,
283+ flexRenderComponent . content ,
236284 componentInjector
237285 )
238- if ( inputs ) {
239- view . setInputs ( inputs )
240- }
286+ if ( inputs ) view . setInputs ( inputs )
241287 return new FlexRenderComponentView ( flexRenderComponent , view )
242288 }
243289
244- #renderCustomComponent( component : Type < unknown > ) : FlexRenderComponentView {
290+ #renderCustomComponent(
291+ component : Extract < FlexRenderTypedContent , { kind : 'component' } >
292+ ) : FlexRenderComponentView {
245293 const view = this . #flexRenderComponentFactory. createComponent (
246- new FlexRenderComponent ( component , this . props ) ,
294+ new FlexRenderComponent ( component . content , this . props ) ,
247295 this . injector
248296 )
249297 view . setInputs ( { ...this . props } )
250298 return new FlexRenderComponentView ( component , view )
251299 }
300+ }
252301
253- #getContentValue( context : TProps ) {
254- const content = this . content
255- const computedContent = computed ( ( ) => {
256- return typeof content !== 'function'
257- ? content
258- : runInInjectionContext ( this . injector , ( ) => content ( context ) )
259- } )
260- return Object . assign ( mapToFlexRenderTypedContent ( computedContent ( ) ) , {
261- computedContent,
262- } )
302+ function logFlags ( place : string , flags : FlexRenderFlags , val : any ) {
303+ console . group ( `${ place } ` , val )
304+ const result = { } as Record < string , boolean >
305+ for ( const key in FlexRenderFlags ) {
306+ // Skip the reverse mapping of numeric values to keys in enums
307+ if ( isNaN ( Number ( key ) ) ) {
308+ const flagValue = FlexRenderFlags [ key as keyof typeof FlexRenderFlags ]
309+ console . log ( key , ! ! ( flags & flagValue ) )
310+ }
263311 }
312+ console . groupEnd ( )
313+ return result
264314}
0 commit comments