@@ -24,6 +24,8 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
2424 // public override componentRef: ComponentRef<any>|null = null;
2525
2626 protected element : IgcNgElement ;
27+ /** The parent _component_'s element (a.k.a the semantic parent, rather than the DOM one after projection) */
28+ protected parentElement ?: WeakRef < IgcNgElement > ;
2729 /** Native Angular parent (if any) the Element is created under, usually as template of dynamic component (e.g. HGrid row island paginator) */
2830 protected angularParent : ComponentRef < any > ;
2931 /** Cached child instances per query prop. Used for dynamic components's child templates that normally persist in Angular runtime */
@@ -41,14 +43,14 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
4143 */
4244 public [ ComponentRefKey ] = new Promise < ComponentRef < any > > ( ( resolve , _ ) => this . setComponentRef = resolve ) ;
4345
44- private _templateWrapper : TemplateWrapperComponent ;
46+ private _templateWrapperRef : ComponentRef < TemplateWrapperComponent > ;
4547 protected get templateWrapper ( ) : TemplateWrapperComponent {
46- if ( ! this . _templateWrapper ) {
48+ if ( ! this . _templateWrapperRef ) {
4749 const componentRef = ( this as any ) . componentRef as ComponentRef < any > ;
4850 const viewRef = componentRef . injector . get ( ViewContainerRef ) ;
49- this . _templateWrapper = viewRef . createComponent ( TemplateWrapperComponent ) . instance ;
51+ this . _templateWrapperRef = viewRef . createComponent ( TemplateWrapperComponent ) ;
5052 }
51- return this . _templateWrapper ;
53+ return this . _templateWrapperRef . instance ;
5254 }
5355
5456 private _configSelectors : string ;
@@ -63,7 +65,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
6365 super ( _componentFactory , _injector ) ;
6466 }
6567
66- override async initializeComponent ( element : HTMLElement ) {
68+ protected override async initializeComponent ( element : HTMLElement ) {
6769 if ( ! element . isConnected ) {
6870 // D.P. 2022-09-20 do not initialize on connectedCallback that is not actually connected
6971 // connectedCallback may be called once your element is no longer connected
@@ -76,14 +78,13 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
7678 // TODO: Fail handling or cancellation needed?
7779 ( this as any ) . componentRef = { } ;
7880
79- const toBeOrphanedChildren = Array . from ( element . children ) . filter ( x => ! this . _componentFactory . ngContentSelectors . some ( sel => x . matches ( sel ) ) ) ;
80- for ( const iterator of toBeOrphanedChildren ) {
81- // TODO: special registration OR config for custom
82- }
81+ // const toBeOrphanedChildren = Array.from(element.children).filter(x => !this._componentFactory.ngContentSelectors.some(sel => x.matches(sel)));
82+ // for (const iterator of toBeOrphanedChildren) {
83+ // // TODO: special registration OR config for custom
84+ // }
8385 let parentInjector : Injector ;
8486 let parentAnchor : ViewContainerRef ;
85- const parents : IgcNgElement [ ] = [ ] ;
86- let parentConfig : ComponentConfig ;
87+ const parents : WeakRef < IgcNgElement > [ ] = [ ] ;
8788 const componentConfig = this . config ?. find ( x => x . component === this . _componentFactory . componentType ) ;
8889
8990 const configParents = componentConfig ?. parents
@@ -98,11 +99,11 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
9899 reflectComponentType ( x . component ) . selector
99100 ] ) . join ( ',' ) ) ;
100101 if ( node ) {
101- parents . push ( node ) ;
102+ parents . push ( new WeakRef ( node ) ) ;
102103 }
103104 }
104105 // select closest of all possible config parents
105- let parent = parents [ 0 ] ;
106+ let parent = parents [ 0 ] ?. deref ( ) ;
106107
107108 // Collected parents may include direct Angular HGrids, so only wait for configured parent elements:
108109 const configParent = configParents . find ( x => x . selector === parent ?. tagName . toLocaleLowerCase ( ) ) ;
@@ -113,15 +114,16 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
113114 // ngElementStrategy getter is protected and also has initialization logic, though that should be safe at this point
114115 if ( parent ?. ngElementStrategy ) {
115116 this . angularParent = parent . ngElementStrategy . angularParent ;
116- const parentComponentRef = await parent ?. ngElementStrategy [ ComponentRefKey ] ;
117+ this . parentElement = new WeakRef ( parent ) ;
118+ let parentComponentRef = await parent ?. ngElementStrategy [ ComponentRefKey ] ;
117119 parentInjector = parentComponentRef ?. injector ;
118120
119121 // TODO: Consider general solution (as in Parent w/ @igxAnchor tag)
120122 if ( element . tagName . toLocaleLowerCase ( ) === 'igc-grid-toolbar'
121123 || element . tagName . toLocaleLowerCase ( ) === 'igc-paginator' ) {
122124 // NOPE: viewcontainerRef will re-render this node again, no option for rootNode :S
123125 // this.componentRef = parentAnchor.createComponent(this.componentFactory.componentType, { projectableNodes, injector: childInjector });
124- const parentComponentRef = await parent ?. ngElementStrategy [ ComponentRefKey ] ;
126+ parentComponentRef = await parent ?. ngElementStrategy [ ComponentRefKey ] ;
125127 parentAnchor = parentComponentRef ?. instance . anchor ;
126128 }
127129 } else if ( ( parent as any ) ?. __componentRef ) {
@@ -182,44 +184,44 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
182184 // componentRef should also likely be protected:
183185 const componentRef = ( this as any ) . componentRef as ComponentRef < any > ;
184186
185- for ( let i = 0 ; i < parents . length ; i ++ ) {
186- const parent = parents [ i ] ;
187-
188- // find the respective config entry
189- parentConfig = configParents . find ( x => x . selector === parent ?. tagName . toLocaleLowerCase ( ) ) ;
190-
191- if ( ! parentConfig ) {
192- continue ;
193- }
187+ const parentQueries = this . getParentContentQueries ( componentConfig , parents , configParents ) ;
194188
195- const componentType = this . _componentFactory . componentType ;
196- // TODO - look into more cases where query expects a certain base class but gets a subclass.
197- // Related to https://github.com/IgniteUI/igniteui-angular/pull/12134#discussion_r983147259
198- const contentQueries = parentConfig . contentQueries . filter ( x => x . childType === componentType || x . childType === componentConfig . provideAs ) ;
199-
200- for ( const query of contentQueries ) {
201- if ( i > 0 && ! query . descendants ) {
202- continue ;
189+ for ( const { parent, query } of parentQueries ) {
190+ if ( query . isQueryList ) {
191+ parent . ngElementStrategy . scheduleQueryUpdate ( query . property ) ;
192+ if ( this . angularParent ) {
193+ // Cache the component in the parent (currently only paginator for HGrid),
194+ // so it is kept in the query even when detached from DOM
195+ this . addToParentCache ( parent , query . property ) ;
203196 }
204-
197+ } else {
205198 const parentRef = await parent . ngElementStrategy [ ComponentRefKey ] ;
206- if ( query . isQueryList ) {
207- parent . ngElementStrategy . scheduleQueryUpdate ( query . property ) ;
208- if ( this . angularParent ) {
209- // Cache the component in the parent (currently only paginator for HGrid),
210- // so it is kept in the query even when detached from DOM
211- this . addToParentCache ( parent , query . property ) ;
212- }
213- } else {
214- parentRef . instance [ query . property ] = componentRef . instance ;
215- parentRef . changeDetectorRef . detectChanges ( ) ;
216- }
199+ parentRef . instance [ query . property ] = componentRef . instance ;
200+ parentRef . changeDetectorRef . detectChanges ( ) ;
217201 }
218202 }
219203
220204 if ( [ 'igc-grid' , 'igc-tree-grid' , 'igc-hierarchical-grid' ] . includes ( element . tagName . toLocaleLowerCase ( ) ) ) {
221205 this . patchGridPopups ( ) ;
222206 }
207+
208+ // instead of duplicating super.disconnect() w/ the scheduled destroy:
209+ componentRef . onDestroy ( ( ) => {
210+ if ( this . _templateWrapperRef ) {
211+ this . _templateWrapperRef . destroy ( ) ;
212+ this . _templateWrapperRef = null ;
213+ }
214+
215+ // also schedule query updates on all parents:
216+ this . getParentContentQueries ( componentConfig , parents , configParents )
217+ . filter ( x => x . parent ?. isConnected && x . query . isQueryList )
218+ . forEach ( ( { parent, query } ) => {
219+ parent . ngElementStrategy . scheduleQueryUpdate ( query . property ) ;
220+ if ( this . angularParent ) {
221+ this . removeFromParentCache ( parent , query . property ) ;
222+ }
223+ } ) ;
224+ } ) ;
223225 }
224226
225227 public override setInputValue ( property : string , value : any , transform ?: ( value : any ) => any ) : void {
@@ -348,23 +350,47 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
348350 }
349351
350352 private addToParentCache ( parentElement : IgcNgElement , queryName : string ) {
351- var cachedComponents = parentElement . ngElementStrategy . cachedChildComponents . get ( queryName ) || [ ] ;
353+ const cachedComponents = parentElement . ngElementStrategy . cachedChildComponents . get ( queryName ) || [ ] ;
352354 cachedComponents . push ( ( this as any ) . componentRef . instance ) ;
353355 parentElement . ngElementStrategy . cachedChildComponents . set ( queryName , cachedComponents ) ;
354356 }
355- //#endregion schedule query update
356357
357- /**
358- * assignTemplateCallback
359- */
360- public assignTemplateCallback ( templateProp : string , callback : any ) {
361- const componentRef = ( this as any ) . componentRef as ComponentRef < any > ;
362- if ( componentRef ) {
363- const templateRef = this . templateWrapper . addTemplate ( callback ) ;
364- componentRef . instance [ templateProp ] = templateRef ;
365- componentRef . changeDetectorRef . detectChanges ( ) ;
358+ private removeFromParentCache ( parentElement : IgcNgElement , queryName : string ) {
359+ let cachedComponents = parentElement . ngElementStrategy . cachedChildComponents . get ( queryName ) || [ ] ;
360+ cachedComponents = cachedComponents . filter ( x => x !== ( this as any ) . componentRef . instance ) ;
361+ parentElement . ngElementStrategy . cachedChildComponents . set ( queryName , cachedComponents ) ;
362+ }
363+
364+ /** Get all matching content questions from all parents */
365+ private getParentContentQueries ( componentConfig : ComponentConfig , parents : WeakRef < IgcNgElement > [ ] , configParents : ComponentConfig [ ] ) : { parent : IgcNgElement , query : ContentQueryMeta } [ ] {
366+ const queries : { parent : IgcNgElement , query : ContentQueryMeta } [ ] = [ ] ;
367+
368+ for ( let i = 0 ; i < parents . length ; i ++ ) {
369+ const parent = parents [ i ] ?. deref ( ) ;
370+
371+ // find the respective config entry
372+ const parentConfig = configParents . find ( x => x . selector === parent ?. tagName . toLocaleLowerCase ( ) ) ;
373+ if ( ! parentConfig ) {
374+ continue ;
375+ }
376+
377+ const componentType = this . _componentFactory . componentType ;
378+ // TODO - look into more cases where query expects a certain base class but gets a subclass.
379+ // Related to https://github.com/IgniteUI/igniteui-angular/pull/12134#discussion_r983147259
380+ const contentQueries = parentConfig . contentQueries . filter ( x => x . childType === componentType || x . childType === componentConfig . provideAs ) ;
381+
382+ for ( const query of contentQueries ) {
383+ if ( i > 0 && ! query . descendants ) {
384+ continue ;
385+ }
386+ queries . push ( { parent, query } ) ;
387+ }
366388 }
389+
390+ return queries ;
367391 }
392+ //#endregion schedule query update
393+
368394
369395 //#region Grid popups hide on scroll
370396 /**
@@ -400,7 +426,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
400426 }
401427 //#endregion
402428
403- override disconnect ( ) : void {
429+ public override disconnect ( ) : void {
404430 if ( this . angularParent ) {
405431 this . angularParent . onDestroy ( ( ) => super . disconnect ( ) ) ;
406432 } else {
0 commit comments