Skip to content

Commit 0ea3413

Browse files
committed
fix(elements): cleanup parent queries on child remove/destroy
1 parent 2d10a05 commit 0ea3413

File tree

2 files changed

+64
-34
lines changed

2 files changed

+64
-34
lines changed

projects/igniteui-angular-elements/src/app/custom-strategy.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ describe('Elements: ', () => {
5252
const gridComponent = (await gridEl.ngElementStrategy[ComponentRefKey]).instance as IgxGridComponent;
5353
const columnComponent = (await columnEl.ngElementStrategy[ComponentRefKey]).instance as IgxColumnComponent;
5454
expect(gridComponent.columnList.toArray()).toContain(columnComponent);
55+
56+
columnEl.remove();
57+
await firstValueFrom(timer(10 /* SCHEDULE_DELAY: DESTROY + QUERY */ * 3));
58+
expect(gridComponent.columnList.toArray()).toEqual([]);
5559
});
5660

5761
it(`should keep IgcNgElement instance in template of another IgcNgElement #15678`, async () => {

projects/igniteui-angular-elements/src/app/custom-strategy.ts

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
8484
// }
8585
let parentInjector: Injector;
8686
let parentAnchor: ViewContainerRef;
87-
const parents: IgcNgElement[] = [];
88-
let parentConfig: ComponentConfig;
87+
const parents: WeakRef<IgcNgElement>[] = [];
8988
const componentConfig = this.config?.find(x => x.component === this._componentFactory.componentType);
9089

9190
const configParents = componentConfig?.parents
@@ -100,11 +99,11 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
10099
reflectComponentType(x.component).selector
101100
]).join(','));
102101
if (node) {
103-
parents.push(node);
102+
parents.push(new WeakRef(node));
104103
}
105104
}
106105
// select closest of all possible config parents
107-
let parent = parents[0];
106+
let parent = parents[0]?.deref();
108107

109108
// Collected parents may include direct Angular HGrids, so only wait for configured parent elements:
110109
const configParent = configParents.find(x => x.selector === parent?.tagName.toLocaleLowerCase());
@@ -185,38 +184,20 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
185184
// componentRef should also likely be protected:
186185
const componentRef = (this as any).componentRef as ComponentRef<any>;
187186

188-
for (let i = 0; i < parents.length; i++) {
189-
const parent = parents[i];
190-
191-
// find the respective config entry
192-
parentConfig = configParents.find(x => x.selector === parent?.tagName.toLocaleLowerCase());
193-
194-
if (!parentConfig) {
195-
continue;
196-
}
187+
const parentQueries = this.getParentContentQueries(componentConfig, parents, configParents);
197188

198-
const componentType = this._componentFactory.componentType;
199-
// TODO - look into more cases where query expects a certain base class but gets a subclass.
200-
// Related to https://github.com/IgniteUI/igniteui-angular/pull/12134#discussion_r983147259
201-
const contentQueries = parentConfig.contentQueries.filter(x => x.childType === componentType || x.childType === componentConfig.provideAs);
202-
203-
for (const query of contentQueries) {
204-
if (i > 0 && !query.descendants) {
205-
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);
206196
}
207-
197+
} else {
208198
const parentRef = await parent.ngElementStrategy[ComponentRefKey];
209-
if (query.isQueryList) {
210-
parent.ngElementStrategy.scheduleQueryUpdate(query.property);
211-
if (this.angularParent) {
212-
// Cache the component in the parent (currently only paginator for HGrid),
213-
// so it is kept in the query even when detached from DOM
214-
this.addToParentCache(parent, query.property);
215-
}
216-
} else {
217-
parentRef.instance[query.property] = componentRef.instance;
218-
parentRef.changeDetectorRef.detectChanges();
219-
}
199+
parentRef.instance[query.property] = componentRef.instance;
200+
parentRef.changeDetectorRef.detectChanges();
220201
}
221202
}
222203

@@ -230,6 +211,16 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
230211
this._templateWrapperRef.destroy();
231212
this._templateWrapperRef = null;
232213
}
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+
});
233224
});
234225
}
235226

@@ -359,10 +350,45 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
359350
}
360351

361352
private addToParentCache(parentElement: IgcNgElement, queryName: string) {
362-
var cachedComponents = parentElement.ngElementStrategy.cachedChildComponents.get(queryName) || [];
353+
const cachedComponents = parentElement.ngElementStrategy.cachedChildComponents.get(queryName) || [];
363354
cachedComponents.push((this as any).componentRef.instance);
364355
parentElement.ngElementStrategy.cachedChildComponents.set(queryName, cachedComponents);
365356
}
357+
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+
}
388+
}
389+
390+
return queries;
391+
}
366392
//#endregion schedule query update
367393

368394

0 commit comments

Comments
 (0)