From 122d8012cd34d6593007643010da9f9a90d09c5f Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 23 Apr 2026 17:35:25 +0300 Subject: [PATCH 1/3] fix(hgrid): add and use lifecycle placeholder for grid connection events. --- .../src/hierarchical-grid.component.html | 5 ++ .../src/hierarchical-grid.component.ts | 60 +++++++++++++++---- .../hierarchical-grid.sample.html | 6 +- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html index 03b1f6c872d..cc67111aa11 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html @@ -276,3 +276,8 @@ } + + + + diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts index fb4c2f4aa85..02d50f63efc 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts @@ -38,6 +38,7 @@ import { IgxPaginatorToken } from 'igniteui-angular/paginator'; import { IgxGridCellMergePipe, IgxGridComponent, IgxGridFilteringPipe, IgxGridSortingPipe, IgxGridUnmergeActivePipe } from 'igniteui-angular/grids/grid'; let NEXT_ID = 0; +const HGRID_LIFECYCLE_PLACEHOLDER_TAG = 'igc-hgrid-lifecycle-placeholder'; /** * @hidden @internal @@ -662,6 +663,7 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti * @hidden */ public override ngOnInit() { + this.registerLifecyclePlaceholderElement(); // this.expansionStatesChange.pipe(takeUntil(this.destroy$)).subscribe((value: Map) => { // const res = Array.from(value.entries()).filter(({1: v}) => v === true).map(([k]) => k); // }); @@ -674,6 +676,26 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti super.ngOnInit(); } + // Event that triggers when element gets connected back to the DOM. + // Used to determine when to reopen a previously closed row editing overlay. + protected onLifecyclePlaceholderConnected(): void { + if (this.rowEditable && this.crudService.rowInEditMode && this.rowEditingOverlay && + this.rowEditingOverlay.collapsed) { + // Row is in edit mode, but overlay is closed - reopen. + this.openRowOverlay(this.crudService.rowInEditMode.id); + } + + } + + // Event that triggers when element gets disconnected from the DOM, for example as a result of virtualization or caching. + // Used to determine when to close the row editing overlay. + protected onLifecyclePlaceholderDisconnected(): void { + if (this.rowEditable && this.crudService.rowInEditMode && this.rowEditingOverlay) { + // disconnected from DOM (possibly cached) & row was in edit mode - close overlay. + this.closeRowEditingOverlay(); + } + } + /** * @hidden */ @@ -720,6 +742,24 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti this.parentIsland.showExpandAll : this.rootGrid.showExpandAll; } + private registerLifecyclePlaceholderElement(): void { + if (!this.platform.isBrowser || typeof customElements === 'undefined' || customElements.get(HGRID_LIFECYCLE_PLACEHOLDER_TAG)) { + return; + } + + class IgxHierarchicalGridLifecyclePlaceholderElement extends HTMLElement { + public connectedCallback(): void { + this.dispatchEvent(new CustomEvent('igcConnected', { bubbles: true, composed: true })); + } + + public disconnectedCallback(): void { + this.dispatchEvent(new CustomEvent('igcDisconnected', { bubbles: true, composed: true })); + } + } + + customElements.define(HGRID_LIFECYCLE_PLACEHOLDER_TAG, IgxHierarchicalGridLifecyclePlaceholderElement); + } + /** * @hidden */ @@ -1232,15 +1272,15 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti { name: null, fields: filterableFields.map(f => ({ - field: f.field, - dataType: f.dataType, - header: f.header, - editorOptions: f.editorOptions, - filters: f.filters, - pipeArgs: f.pipeArgs, - defaultTimeFormat: f.defaultTimeFormat, - defaultDateTimeFormat: f.defaultDateTimeFormat - })) as FieldType[] + field: f.field, + dataType: f.dataType, + header: f.header, + editorOptions: f.editorOptions, + filters: f.filters, + pipeArgs: f.pipeArgs, + defaultTimeFormat: f.defaultTimeFormat, + defaultDateTimeFormat: f.defaultDateTimeFormat + })) as FieldType[] } ]; @@ -1249,7 +1289,7 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti this.data[0][rowIsland.key][0] : null; return acc.concat(this.generateChildEntity(rowIsland, childFirstRowData)); } - , []); + , []); } return entities; diff --git a/src/app/hierarchical-grid/hierarchical-grid.sample.html b/src/app/hierarchical-grid/hierarchical-grid.sample.html index 81ee690a48f..45835fc2cf3 100644 --- a/src/app/hierarchical-grid/hierarchical-grid.sample.html +++ b/src/app/hierarchical-grid/hierarchical-grid.sample.html @@ -146,12 +146,12 @@

Sample two

- - - + From ad14b90cefbfdc16029626c066b6dad928436dcb Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 23 Apr 2026 18:53:48 +0300 Subject: [PATCH 2/3] chore(*): Fix virtualization issue with change detection. --- .../directives/src/directives/for-of/for_of.directive.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/directives/src/directives/for-of/for_of.directive.ts b/projects/igniteui-angular/directives/src/directives/for-of/for_of.directive.ts index c90cb47014e..4ca64e19e49 100644 --- a/projects/igniteui-angular/directives/src/directives/for-of/for_of.directive.ts +++ b/projects/igniteui-angular/directives/src/directives/for-of/for_of.directive.ts @@ -1065,7 +1065,7 @@ export class IgxForOfDirective extends IgxForOfToken node.nodeType === Node.ELEMENT_NODE) || embView.rootNodes[0].nextElementSibling); const view = container.detach(0); - + view.detectChanges(); this.updateTemplateContext(embView.context, i); container.insert(view); this._embeddedViews.push(embView); @@ -1085,7 +1085,7 @@ export class IgxForOfDirective extends IgxForOfToken node.nodeType === Node.ELEMENT_NODE) || embView.rootNodes[0].nextElementSibling); const view = container.detach(container.length - 1); - + view.detectChanges(); this.updateTemplateContext(embView.context, i); container.insert(view, 0); this._embeddedViews.unshift(embView); From 6281d2b1fbacd45835a1eb082ed1d2f90b84b24e Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 27 Apr 2026 13:55:22 +0300 Subject: [PATCH 3/3] chore(*): Apply review comments. --- .../src/hierarchical-grid.component.html | 4 ++-- .../src/hierarchical-grid.component.ts | 24 ++++--------------- .../src/lifecycle-placeholder-element.ts | 23 ++++++++++++++++++ 3 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 projects/igniteui-angular/grids/hierarchical-grid/src/lifecycle-placeholder-element.ts diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html index cc67111aa11..cbc9fc39999 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html @@ -278,6 +278,6 @@ } - - + diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts index 02d50f63efc..eb934967039 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts @@ -36,9 +36,9 @@ import { IgxIconComponent } from 'igniteui-angular/icon'; import { EntityType, FieldType, IFilteringExpressionsTree, IgxActionStripToken, IgxOverlayOutletDirective, flatten, IGridResourceStrings } from 'igniteui-angular/core'; import { IgxPaginatorToken } from 'igniteui-angular/paginator'; import { IgxGridCellMergePipe, IgxGridComponent, IgxGridFilteringPipe, IgxGridSortingPipe, IgxGridUnmergeActivePipe } from 'igniteui-angular/grids/grid'; +import { registerLifecyclePlaceholderElement } from './lifecycle-placeholder-element'; let NEXT_ID = 0; -const HGRID_LIFECYCLE_PLACEHOLDER_TAG = 'igc-hgrid-lifecycle-placeholder'; /** * @hidden @internal @@ -663,7 +663,9 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti * @hidden */ public override ngOnInit() { - this.registerLifecyclePlaceholderElement(); + if (this.platform.isBrowser) { + registerLifecyclePlaceholderElement(); + } // this.expansionStatesChange.pipe(takeUntil(this.destroy$)).subscribe((value: Map) => { // const res = Array.from(value.entries()).filter(({1: v}) => v === true).map(([k]) => k); // }); @@ -742,24 +744,6 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti this.parentIsland.showExpandAll : this.rootGrid.showExpandAll; } - private registerLifecyclePlaceholderElement(): void { - if (!this.platform.isBrowser || typeof customElements === 'undefined' || customElements.get(HGRID_LIFECYCLE_PLACEHOLDER_TAG)) { - return; - } - - class IgxHierarchicalGridLifecyclePlaceholderElement extends HTMLElement { - public connectedCallback(): void { - this.dispatchEvent(new CustomEvent('igcConnected', { bubbles: true, composed: true })); - } - - public disconnectedCallback(): void { - this.dispatchEvent(new CustomEvent('igcDisconnected', { bubbles: true, composed: true })); - } - } - - customElements.define(HGRID_LIFECYCLE_PLACEHOLDER_TAG, IgxHierarchicalGridLifecyclePlaceholderElement); - } - /** * @hidden */ diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/lifecycle-placeholder-element.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/lifecycle-placeholder-element.ts new file mode 100644 index 00000000000..e22c54aaee2 --- /dev/null +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/lifecycle-placeholder-element.ts @@ -0,0 +1,23 @@ +const LIFECYCLE_PLACEHOLDER_TAG = 'igc-lifecycle-placeholder'; +const LIFECYCLE_CONNECTED_EVENT = 'igcConnected'; +const LIFECYCLE_DISCONNECTED_EVENT = 'igcDisconnected'; + +/** @hidden @internal */ +export function registerLifecyclePlaceholderElement(): void { + if (typeof customElements === 'undefined' || typeof HTMLElement === 'undefined' || + typeof CustomEvent === 'undefined' || customElements.get(LIFECYCLE_PLACEHOLDER_TAG)) { + return; + } + + class LifecyclePlaceholderElement extends HTMLElement { + public connectedCallback(): void { + this.dispatchEvent(new CustomEvent(LIFECYCLE_CONNECTED_EVENT)); + } + + public disconnectedCallback(): void { + this.dispatchEvent(new CustomEvent(LIFECYCLE_DISCONNECTED_EVENT)); + } + } + + customElements.define(LIFECYCLE_PLACEHOLDER_TAG, LifecyclePlaceholderElement); +}