Skip to content

Commit ce789d5

Browse files
fix(elements): Do not destroy elements inside tmpl wrapper on detach. (#15678)
Co-authored-by: Damyan Petev <[email protected]>
1 parent dd4d73b commit ce789d5

File tree

4 files changed

+64
-2
lines changed

4 files changed

+64
-2
lines changed

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IgxColumnComponent, IgxGridComponent, IgxHierarchicalGridComponent } from 'igniteui-angular';
2+
import { html } from 'lit-html';
23
import { firstValueFrom, fromEvent, skip, timer } from 'rxjs';
34
import { ComponentRefKey, IgcNgElement } from './custom-strategy';
45
import hgridData from '../assets/data/projects-hgrid.js';
@@ -52,6 +53,43 @@ describe('Elements: ', () => {
5253
const columnComponent = (await columnEl.ngElementStrategy[ComponentRefKey]).instance as IgxColumnComponent;
5354
expect(gridComponent.columnList.toArray()).toContain(columnComponent);
5455
});
56+
57+
it(`should keep IgcNgElement instance in template of another IgcNgElement #15678`, async () => {
58+
const gridEl = document.createElement("igc-grid");
59+
testContainer.appendChild(gridEl);
60+
const columnEl = document.createElement("igc-column") as IgcNgElement;
61+
gridEl.appendChild(columnEl);
62+
gridEl.primaryKey = 'id';
63+
gridEl.data = [{ id: '1' }];
64+
(gridEl as any).detailTemplate = (ctx) => {
65+
return html`<div>
66+
<igc-grid id="child${ctx.implicit.id}"></igc-grid>
67+
</div>`;
68+
}
69+
70+
// TODO: Better way to wait - potentially expose the queue or observable for update on the strategy
71+
await firstValueFrom(timer(10 /* SCHEDULE_DELAY */ * 2));
72+
73+
// sigh (。﹏。*)
74+
(gridEl as any).toggleRow('1');
75+
await firstValueFrom(timer(10 /* SCHEDULE_DELAY */ * 2));
76+
77+
let detailGrid = document.querySelector<IgcNgElement>('#child1');
78+
expect(detailGrid).toBeDefined();
79+
let detailGridComponent = (await detailGrid?.ngElementStrategy[ComponentRefKey])?.instance as IgxGridComponent;
80+
expect(detailGridComponent).toBeDefined();
81+
82+
// close and re-expand row detail:
83+
(gridEl as any).toggleRow('1');
84+
await firstValueFrom(timer(10 /* SCHEDULE_DELAY */ * 2));
85+
(gridEl as any).toggleRow('1');
86+
await firstValueFrom(timer(10 /* SCHEDULE_DELAY */ * 2));
87+
88+
detailGrid = document.querySelector<IgcNgElement>('#child1');
89+
expect(detailGrid).toBeDefined();
90+
detailGridComponent = (await detailGrid?.ngElementStrategy[ComponentRefKey])?.instance as IgxGridComponent;
91+
expect(detailGridComponent).toBeDefined("Detail child grid was destroyed on re-expand");
92+
});
5593
});
5694

5795
describe('Grid integration scenarios.', () => {

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, Injector, OnChanges, QueryList, Type, ViewContainerRef, reflectComponentType } from '@angular/core';
1+
import { ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, DestroyRef, Injector, OnChanges, QueryList, Type, ViewContainerRef, reflectComponentType } from '@angular/core';
2+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
23
import { NgElement } from '@angular/elements';
34
import { fromEvent } from 'rxjs';
45
import { takeUntil } from 'rxjs/operators';
@@ -48,6 +49,14 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
4849
return this._templateWrapper;
4950
}
5051

52+
private _configSelectors: string;
53+
public get configSelectors(): string {
54+
if (!this._configSelectors) {
55+
this._configSelectors = this.config.map(x => x.selector).join(',');
56+
}
57+
return this._configSelectors;
58+
}
59+
5160
constructor(private _componentFactory: ComponentFactory<any>, private _injector: Injector, private config: ComponentConfig[]) {
5261
super(_componentFactory, _injector);
5362
}
@@ -233,6 +242,14 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
233242
}
234243
value = this.templateWrapper.addTemplate(value);
235244
// TODO: discard oldValue
245+
246+
// check template for any angular-element components
247+
this.templateWrapper.templateRendered.pipe(takeUntilDestroyed(componentRef.injector.get(DestroyRef))).subscribe((element) => {
248+
element.querySelectorAll<IgcNgElement>(this.configSelectors)?.forEach((c) => {
249+
// tie to angularParent lifecycle for cached scenarios like detailTemplate:
250+
c.ngElementStrategy.angularParent = componentRef;
251+
});
252+
});
236253
}
237254
if (componentRef && componentConfig?.boolProps?.includes(property)) {
238255
// bool coerce:

projects/igniteui-angular-elements/src/app/wrapper/wrapper.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ChangeDetectorRef, Component, QueryList, TemplateRef, ViewChildren } from '@angular/core';
2+
import { Subject } from 'rxjs';
23
import { TemplateRefWrapper } from './template-ref-wrapper';
34

45
import { render, TemplateResult } from 'lit-html';
@@ -14,6 +15,7 @@ type TemplateFunction = (arg: any) => TemplateResult;
1415
export class TemplateWrapperComponent {
1516

1617
public templateFunctions: TemplateFunction[] = [];
18+
public templateRendered = new Subject<HTMLElement>();
1719

1820
/**
1921
* All template refs
@@ -27,6 +29,7 @@ export class TemplateWrapperComponent {
2729

2830
public litRender(container: HTMLElement, templateFunc: (arg: any) => TemplateResult, arg: any) {
2931
render(templateFunc(arg), container);
32+
this.templateRendered.next(container);
3033
}
3134

3235
public addTemplate(templateFunc: TemplateFunction): TemplateRef<any> {

projects/igniteui-angular-elements/src/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,11 @@ <h3 class="ig-typography__h6">Flat Grid (MRL column layout)</h3>
195195
<igc-combo ${ref(focusCallback)} style="width:100%; height:100%" .data=${northwindProducts} value-key="ProductName" @igcChange=${(e) => ctx.cell.editValue = e.detail.newValue} single-select>
196196
</igc-combo>
197197
`;
198-
grid1.detailTemplate = (ctx) => html`<div><span class="categoryStyle">Stock: ${ctx.implicit.InStock}</span></div>`;
198+
grid1.detailTemplate = (ctx) => {
199+
return html`<div>
200+
<igc-grid auto-generate="true"></igc-grid>
201+
</div>`;
202+
}
199203

200204
grid2.querySelector('igc-column[field="ProductName"]').inlineEditorTemplate = (ctx) =>
201205
html`

0 commit comments

Comments
 (0)