@@ -24,6 +24,8 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
24
24
// public override componentRef: ComponentRef<any>|null = null;
25
25
26
26
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 > ;
27
29
/** Native Angular parent (if any) the Element is created under, usually as template of dynamic component (e.g. HGrid row island paginator) */
28
30
protected angularParent : ComponentRef < any > ;
29
31
/** 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 {
41
43
*/
42
44
public [ ComponentRefKey ] = new Promise < ComponentRef < any > > ( ( resolve , _ ) => this . setComponentRef = resolve ) ;
43
45
44
- private _templateWrapper : TemplateWrapperComponent ;
46
+ private _templateWrapperRef : ComponentRef < TemplateWrapperComponent > ;
45
47
protected get templateWrapper ( ) : TemplateWrapperComponent {
46
- if ( ! this . _templateWrapper ) {
48
+ if ( ! this . _templateWrapperRef ) {
47
49
const componentRef = ( this as any ) . componentRef as ComponentRef < any > ;
48
50
const viewRef = componentRef . injector . get ( ViewContainerRef ) ;
49
- this . _templateWrapper = viewRef . createComponent ( TemplateWrapperComponent ) . instance ;
51
+ this . _templateWrapperRef = viewRef . createComponent ( TemplateWrapperComponent ) ;
50
52
}
51
- return this . _templateWrapper ;
53
+ return this . _templateWrapperRef . instance ;
52
54
}
53
55
54
56
private _configSelectors : string ;
@@ -63,7 +65,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
63
65
super ( _componentFactory , _injector ) ;
64
66
}
65
67
66
- override async initializeComponent ( element : HTMLElement ) {
68
+ protected override async initializeComponent ( element : HTMLElement ) {
67
69
if ( ! element . isConnected ) {
68
70
// D.P. 2022-09-20 do not initialize on connectedCallback that is not actually connected
69
71
// connectedCallback may be called once your element is no longer connected
@@ -76,14 +78,13 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
76
78
// TODO: Fail handling or cancellation needed?
77
79
( this as any ) . componentRef = { } ;
78
80
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
+ // }
83
85
let parentInjector : Injector ;
84
86
let parentAnchor : ViewContainerRef ;
85
- const parents : IgcNgElement [ ] = [ ] ;
86
- let parentConfig : ComponentConfig ;
87
+ const parents : WeakRef < IgcNgElement > [ ] = [ ] ;
87
88
const componentConfig = this . config ?. find ( x => x . component === this . _componentFactory . componentType ) ;
88
89
89
90
const configParents = componentConfig ?. parents
@@ -98,11 +99,11 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
98
99
reflectComponentType ( x . component ) . selector
99
100
] ) . join ( ',' ) ) ;
100
101
if ( node ) {
101
- parents . push ( node ) ;
102
+ parents . push ( new WeakRef ( node ) ) ;
102
103
}
103
104
}
104
105
// select closest of all possible config parents
105
- let parent = parents [ 0 ] ;
106
+ let parent = parents [ 0 ] ?. deref ( ) ;
106
107
107
108
// Collected parents may include direct Angular HGrids, so only wait for configured parent elements:
108
109
const configParent = configParents . find ( x => x . selector === parent ?. tagName . toLocaleLowerCase ( ) ) ;
@@ -113,15 +114,16 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
113
114
// ngElementStrategy getter is protected and also has initialization logic, though that should be safe at this point
114
115
if ( parent ?. ngElementStrategy ) {
115
116
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 ] ;
117
119
parentInjector = parentComponentRef ?. injector ;
118
120
119
121
// TODO: Consider general solution (as in Parent w/ @igxAnchor tag)
120
122
if ( element . tagName . toLocaleLowerCase ( ) === 'igc-grid-toolbar'
121
123
|| element . tagName . toLocaleLowerCase ( ) === 'igc-paginator' ) {
122
124
// NOPE: viewcontainerRef will re-render this node again, no option for rootNode :S
123
125
// this.componentRef = parentAnchor.createComponent(this.componentFactory.componentType, { projectableNodes, injector: childInjector });
124
- const parentComponentRef = await parent ?. ngElementStrategy [ ComponentRefKey ] ;
126
+ parentComponentRef = await parent ?. ngElementStrategy [ ComponentRefKey ] ;
125
127
parentAnchor = parentComponentRef ?. instance . anchor ;
126
128
}
127
129
} else if ( ( parent as any ) ?. __componentRef ) {
@@ -182,44 +184,44 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
182
184
// componentRef should also likely be protected:
183
185
const componentRef = ( this as any ) . componentRef as ComponentRef < any > ;
184
186
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 ) ;
194
188
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 ) ;
203
196
}
204
-
197
+ } else {
205
198
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 ( ) ;
217
201
}
218
202
}
219
203
220
204
if ( [ 'igc-grid' , 'igc-tree-grid' , 'igc-hierarchical-grid' ] . includes ( element . tagName . toLocaleLowerCase ( ) ) ) {
221
205
this . patchGridPopups ( ) ;
222
206
}
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
+ } ) ;
223
225
}
224
226
225
227
public override setInputValue ( property : string , value : any , transform ?: ( value : any ) => any ) : void {
@@ -348,23 +350,47 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
348
350
}
349
351
350
352
private addToParentCache ( parentElement : IgcNgElement , queryName : string ) {
351
- var cachedComponents = parentElement . ngElementStrategy . cachedChildComponents . get ( queryName ) || [ ] ;
353
+ const cachedComponents = parentElement . ngElementStrategy . cachedChildComponents . get ( queryName ) || [ ] ;
352
354
cachedComponents . push ( ( this as any ) . componentRef . instance ) ;
353
355
parentElement . ngElementStrategy . cachedChildComponents . set ( queryName , cachedComponents ) ;
354
356
}
355
- //#endregion schedule query update
356
357
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
+ }
366
388
}
389
+
390
+ return queries ;
367
391
}
392
+ //#endregion schedule query update
393
+
368
394
369
395
//#region Grid popups hide on scroll
370
396
/**
@@ -400,7 +426,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
400
426
}
401
427
//#endregion
402
428
403
- override disconnect ( ) : void {
429
+ public override disconnect ( ) : void {
404
430
if ( this . angularParent ) {
405
431
this . angularParent . onDestroy ( ( ) => super . disconnect ( ) ) ;
406
432
} else {
0 commit comments