From fbfdf939222a0cbf1b90e195e53b71c90f30d82c Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 22 Jan 2026 16:52:47 +0200 Subject: [PATCH 01/35] chore(*): Wrap each row in container w resize observer and update. --- .../src/directives/for-of/for_of.directive.ts | 70 ++++++++++++++++--- .../grids/grid/src/grid.component.html | 2 + 2 files changed, 64 insertions(+), 8 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 be846b90623..26861ae0818 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 @@ -262,7 +262,7 @@ export class IgxForOfDirective extends IgxForOfToken> = []; - protected contentResizeNotify = new Subject(); + protected contentResizeNotify = new Subject(); protected contentObserver: ResizeObserver; /** Size that is being virtualized. */ protected _virtSize = 0; @@ -460,9 +460,8 @@ export class IgxForOfDirective extends IgxForOfToken(this.destroy$); this.contentResizeNotify.pipe( filter(() => this.igxForContainerSize && this.igxForOf && this.igxForOf.length > 0), - throttleTime(40, undefined, { leading: false, trailing: true }), destructor - ).subscribe(() => this._zone.runTask(() => this.updateSizes())); + ).subscribe((entries: ResizeObserverEntry[]) => this._zone.runTask(() => this.updateSizes(entries))); } if (this.igxForScrollOrientation === 'horizontal') { @@ -487,11 +486,15 @@ export class IgxForOfDirective extends IgxForOfToken { if (this.platformUtil.isBrowser) { - this.contentObserver = new (getResizeObserver())(() => this.contentResizeNotify.next()); - this.contentObserver.observe(this.dc.instance._viewContainer.element.nativeElement); + this.contentObserver = new (getResizeObserver())((entries: ResizeObserverEntry[]) => this.contentResizeNotify.next(entries)); + this.contentObserver.observe(target); } }); } @@ -801,6 +804,48 @@ export class IgxForOfDirective extends IgxForOfToken 5; } + public updateSizeAtIndex(index: number, newSize: number) { + const oldSize = this.individualSizeCache[index]; + const sizeDiff = newSize - oldSize; + this.individualSizeCache[index] = newSize; + if (sizeDiff === 0) { + return; + } + for (let j = index + 1; j < this.sizesCache.length; j++) { + this.sizesCache[j] = (this.sizesCache[j] || 0) + sizeDiff; + } + + // update scrBar heights/widths + const reducer = (acc, val) => acc + val; + + const hSum = this.individualSizeCache.reduce(reducer); + if (hSum > this._maxSize) { + this._virtRatio = hSum / this._maxSize; + } + this.scrollComponent.size = Math.min(this.scrollComponent.size + sizeDiff, this._maxSize); + this._virtSize = hSum; + if (!this.scrollComponent.destroyed) { + this.scrollComponent.cdr.detectChanges(); + } + const scrToBottom = this._isScrolledToBottom && !this.dc.instance.notVirtual; + if (scrToBottom && !this._isAtBottomIndex) { + const containerSize = parseInt(this.igxForContainerSize, 10); + const maxVirtScrollTop = this._virtSize - containerSize; + this._bScrollInternal = true; + this._virtScrollPosition = maxVirtScrollTop; + this.scrollPosition = maxVirtScrollTop; + return; + } + if (this._adjustToIndex) { + // in case scrolled to specific index where after scroll heights are changed + // need to adjust the offsets so that item is last in view. + if (sizeDiff !== 0) { + this.addScroll(sizeDiff); + } + this._adjustToIndex = null; + } + } + /** * @hidden * Function that recalculates and updates cache sizes. @@ -934,10 +979,17 @@ export class IgxForOfDirective extends IgxForOfToken { + if (entry.target.isConnected) { + const index = parseInt(entry.target.getAttribute('data-index'), 0); + this.updateSizeAtIndex(index, entry.contentRect.height); + + console.log(`Index: ${index}, New Size: ${entry.contentRect.height}`); + } + }); this._applyChanges(); this._updateScrollOffset(); if (scrollable !== this.isScrollable()) { @@ -1389,6 +1441,7 @@ export class IgxForOfDirective extends IgxForOfToken node.nodeType === Node.ELEMENT_NODE) || oldElem.rootNodes[0].nextElementSibling); // also detach from ViewContainerRef to make absolutely sure this is removed from the view container. this.dc.instance._vcr.detach(this.dc.instance._vcr.length - 1); oldElem.destroy(); @@ -1689,7 +1742,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec this.dc.instance._viewContainer.element.nativeElement.style.top = -(scrollOffset) + 'px'; - this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); + // this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); this.cdr.markForCheck(); } @@ -1816,6 +1869,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec ); this._embeddedViews.push(embeddedView); + this.subscribeToObserver(embeddedView.rootNodes.find(node => node.nodeType === Node.ELEMENT_NODE) || embeddedView.rootNodes[0].nextElementSibling); this.state.chunkSize++; } diff --git a/projects/igniteui-angular/grids/grid/src/grid.component.html b/projects/igniteui-angular/grids/grid/src/grid.component.html index 9e96d118d99..9efef0a7b42 100644 --- a/projects/igniteui-angular/grids/grid/src/grid.component.html +++ b/projects/igniteui-angular/grids/grid/src/grid.component.html @@ -104,6 +104,7 @@ [igxForItemSize]="hasColumnLayouts ? rowHeight * multiRowLayoutRowSize + 1 : renderedRowHeight" [igxForTrackBy]="trackChanges" #verticalScrollContainer (chunkPreload)="dataLoading($event)" (dataChanging)="dataRebinding($event)" (dataChanged)="dataRebound($event)"> +
+
From 80fdb9fdee2e9cc8d881816a991c38fa7451f724 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 26 Jan 2026 12:58:13 +0200 Subject: [PATCH 02/35] chore(*): Add cache for view sizes, use when updating caches after scroll. --- .../src/directives/for-of/for_of.directive.ts | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 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 64ac26e6bdf..d3ac718151e 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 @@ -95,6 +95,7 @@ export class IgxForOfDirective extends IgxForOfToken, number>(); /** * Sets the data to be rendered. @@ -868,32 +869,28 @@ export class IgxForOfDirective extends IgxForOfToken 0) { for (let j = this.state.startIndex + this.state.chunkSize + 1; j < this.sizesCache.length; j++) { @@ -1011,7 +1008,8 @@ export class IgxForOfDirective extends IgxForOfToken extends IgxForOfDirec return this.igxForSizePropName || 'height'; } - public override recalcUpdateSizes() { + public override recalcUpdateSizes(prevState: IForOfState) { if (this.igxGridForOfVariableSizes && this.igxForScrollOrientation === 'vertical') { - super.recalcUpdateSizes(); + super.recalcUpdateSizes(prevState); } } @@ -1767,12 +1765,13 @@ export class IgxGridForOfDirective extends IgxForOfDirec } else { this._bScrollInternal = false; } + const prevState = Object.assign({}, this.state); const scrollOffset = this.fixedUpdateAllElements(this._virtScrollPosition); runInInjectionContext(this._injector, () => { afterNextRender({ write: () => { this.dc.instance._viewContainer.element.nativeElement.style.transform = `translateY(${-scrollOffset}px)`; - this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); + this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this, prevState)); } }); }); From 1080c0f51f7552f1df2747b1acdad390b8b4d45e Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 26 Jan 2026 13:03:01 +0200 Subject: [PATCH 03/35] chore(*): Clean console logs. --- .../directives/src/directives/for-of/for_of.directive.ts | 2 -- 1 file changed, 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 d3ac718151e..78c274794c6 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 @@ -874,7 +874,6 @@ export class IgxForOfDirective extends IgxForOfToken extends IgxForOfToken Date: Mon, 26 Jan 2026 13:12:42 +0200 Subject: [PATCH 04/35] chore(*): Fix lint error. --- .../directives/src/directives/for-of/for_of.directive.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 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 78c274794c6..0653e79ebe6 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 @@ -1,5 +1,5 @@ import { NgForOfContext } from '@angular/common'; -import { ChangeDetectorRef, ComponentRef, Directive, EmbeddedViewRef, EventEmitter, Input, IterableChanges, IterableDiffer, IterableDiffers, NgZone, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, TemplateRef, TrackByFunction, ViewContainerRef, AfterViewInit, booleanAttribute, DOCUMENT, inject, afterNextRender, runInInjectionContext, EnvironmentInjector } from '@angular/core'; +import { ChangeDetectorRef, ComponentRef, Directive, EmbeddedViewRef, EventEmitter, Input, IterableChanges, IterableDiffer, IterableDiffers, NgZone, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, TemplateRef, TrackByFunction, ViewContainerRef, booleanAttribute, DOCUMENT, inject, afterNextRender, runInInjectionContext, EnvironmentInjector } from '@angular/core'; import { DisplayContainerComponent } from './display.container'; import { HVirtualHelperComponent } from './horizontal.virtual.helper.component'; @@ -84,7 +84,7 @@ export abstract class IgxForOfToken { ], standalone: true }) -export class IgxForOfDirective extends IgxForOfToken implements OnInit, OnChanges, OnDestroy, AfterViewInit { +export class IgxForOfDirective extends IgxForOfToken implements OnInit, OnChanges, OnDestroy { private _viewContainer = inject(ViewContainerRef); protected _template = inject>>(TemplateRef); protected _differs = inject(IterableDiffers); @@ -498,10 +498,6 @@ export class IgxForOfDirective extends IgxForOfToken { From 7d664438ff35a41b825f3fdee86a249bd276904b Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 26 Jan 2026 16:01:58 +0200 Subject: [PATCH 05/35] chore(*): Make override arg optional too. --- .../directives/src/directives/for-of/for_of.directive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0653e79ebe6..3e512544677 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 @@ -1643,7 +1643,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec return this.igxForSizePropName || 'height'; } - public override recalcUpdateSizes(prevState: IForOfState) { + public override recalcUpdateSizes(prevState?: IForOfState) { if (this.igxGridForOfVariableSizes && this.igxForScrollOrientation === 'vertical') { super.recalcUpdateSizes(prevState); } From 2d96c12169cad6e0c6bce853f28bdc66d48c99b1 Mon Sep 17 00:00:00 2001 From: MKirova Date: Tue, 27 Jan 2026 11:42:05 +0200 Subject: [PATCH 06/35] chore(*): Update cache for all views but update size cached only for active. --- .../src/directives/for-of/for_of.directive.ts | 58 +++---------------- 1 file changed, 9 insertions(+), 49 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 3e512544677..b25a34a139f 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 @@ -819,47 +819,6 @@ export class IgxForOfDirective extends IgxForOfToken 5; } - public updateSizeAtIndex(index: number, newSize: number) { - const oldSize = this.individualSizeCache[index]; - const sizeDiff = newSize - oldSize; - this.individualSizeCache[index] = newSize; - if (sizeDiff === 0) { - return; - } - for (let j = index + 1; j < this.sizesCache.length; j++) { - this.sizesCache[j] = (this.sizesCache[j] || 0) + sizeDiff; - } - - // update scrBar heights/widths - const reducer = (acc, val) => acc + val; - - const hSum = this.individualSizeCache.reduce(reducer); - if (hSum > this._maxSize) { - this._virtRatio = hSum / this._maxSize; - } - this.scrollComponent.size = Math.min(this.scrollComponent.size + sizeDiff, this._maxSize); - this._virtSize = hSum; - if (!this.scrollComponent.destroyed) { - this.scrollComponent.cdr.detectChanges(); - } - const scrToBottom = this._isScrolledToBottom && !this.dc.instance.notVirtual; - if (scrToBottom && !this._isAtBottomIndex) { - const containerSize = parseInt(this.igxForContainerSize, 10); - const maxVirtScrollTop = this._virtSize - containerSize; - this._bScrollInternal = true; - this._virtScrollPosition = maxVirtScrollTop; - this.scrollPosition = maxVirtScrollTop; - return; - } - if (this._adjustToIndex) { - // in case scrolled to specific index where after scroll heights are changed - // need to adjust the offsets so that item is last in view. - if (sizeDiff !== 0) { - this.addScroll(sizeDiff); - } - this._adjustToIndex = null; - } - } /** * @hidden @@ -879,13 +838,15 @@ export class IgxForOfDirective extends IgxForOfToken 0) { for (let j = this.state.startIndex + this.state.chunkSize + 1; j < this.sizesCache.length; j++) { @@ -1000,15 +961,14 @@ export class IgxForOfDirective extends IgxForOfToken { - if (entry.target.isConnected) { - const index = parseInt(entry.target.getAttribute('data-index'), 0); - this.updateSizeAtIndex(index, entry.contentRect.height); - const embView = this._embeddedViews[index - this.state.startIndex]; - this._embeddedViewSizesCache.set(embView, entry.contentRect.height); - } + const index = parseInt(entry.target.getAttribute('data-index'), 0); + const height = entry.contentRect.height; + const embView = this._embeddedViews[index - this.state.startIndex]; + this._embeddedViewSizesCache.set(embView, height); }); this._applyChanges(); this._updateScrollOffset(); + this.recalcUpdateSizes(); if (scrollable !== this.isScrollable()) { this.scrollbarVisibilityChanged.emit(); } else { From 807ef0be2111a84b5da32e94b951e1a77b658b64 Mon Sep 17 00:00:00 2001 From: MKirova Date: Tue, 27 Jan 2026 17:10:41 +0200 Subject: [PATCH 07/35] chore(*): Use resize observer only for measurements. --- .../src/directives/for-of/for_of.directive.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 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 b25a34a139f..5c0287b76f1 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 @@ -501,6 +501,7 @@ export class IgxForOfDirective extends IgxForOfToken { + // console.log(target); if (this.platformUtil.isBrowser) { this.contentObserver = new (getResizeObserver())((entries: ResizeObserverEntry[]) => this.contentResizeNotify.next(entries)); this.contentObserver.observe(target); @@ -842,11 +843,16 @@ export class IgxForOfDirective extends IgxForOfToken 0) { + // console.log('RECALC Size increased for index ', targetIndex, ' from ', oldVal, ' to ', newVal); + // } diffs.push(currDiff); totalDiff += currDiff; this.individualSizeCache[targetIndex] = cachedNodeSize; this.sizesCache[targetIndex + 1] = (this.sizesCache[targetIndex] || 0) + newVal; } + + // console.log(this.individualSizeCache); // update cache if (Math.abs(totalDiff) > 0) { for (let j = this.state.startIndex + this.state.chunkSize + 1; j < this.sizesCache.length; j++) { @@ -958,22 +964,15 @@ export class IgxForOfDirective extends IgxForOfToken { const index = parseInt(entry.target.getAttribute('data-index'), 0); const height = entry.contentRect.height; const embView = this._embeddedViews[index - this.state.startIndex]; this._embeddedViewSizesCache.set(embView, height); + // console.log('Observed size change for index ', index, ' new size ', height); + // console.log(this._embeddedViewSizesCache); }); - this._applyChanges(); - this._updateScrollOffset(); this.recalcUpdateSizes(); - if (scrollable !== this.isScrollable()) { - this.scrollbarVisibilityChanged.emit(); - } else { - this.contentSizeChange.emit(); - } } /** @@ -1423,6 +1422,7 @@ export class IgxForOfDirective extends IgxForOfToken Date: Tue, 27 Jan 2026 17:13:35 +0200 Subject: [PATCH 08/35] chore(*): Remove unnecessary recalc. --- .../directives/src/directives/for-of/for_of.directive.ts | 1 - 1 file changed, 1 deletion(-) 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 5c0287b76f1..d8942ced9da 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 @@ -972,7 +972,6 @@ export class IgxForOfDirective extends IgxForOfToken Date: Tue, 27 Jan 2026 17:19:05 +0200 Subject: [PATCH 09/35] chore(*): Add back since it still needs update in some scenarios. --- .../directives/src/directives/for-of/for_of.directive.ts | 1 + 1 file changed, 1 insertion(+) 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 d8942ced9da..5c0287b76f1 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 @@ -972,6 +972,7 @@ export class IgxForOfDirective extends IgxForOfToken Date: Wed, 28 Jan 2026 10:08:36 +0200 Subject: [PATCH 10/35] chore(*): Add sample with hgrid for measuring perf. --- .../src/app/app.routes.ts | 9 ++++- .../hierarchical-grid.component.html | 35 ++++++++++++++++ .../hierarchical-grid.component.scss | 10 +++++ .../hierarchical-grid.component.ts | 40 +++++++++++++++++++ .../src/app/services/data.service.ts | 12 +++++- 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.html create mode 100644 projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.scss create mode 100644 projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.ts diff --git a/projects/igniteui-angular-performance/src/app/app.routes.ts b/projects/igniteui-angular-performance/src/app/app.routes.ts index 190c44ecdb4..5804152b12b 100644 --- a/projects/igniteui-angular-performance/src/app/app.routes.ts +++ b/projects/igniteui-angular-performance/src/app/app.routes.ts @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; import { GridComponent } from './grid/grid.component'; import { TreeGridComponent } from './tree-grid/tree-grid.component'; import { PivotGridComponent } from './pivot-grid/pivot-grid.component'; +import { HierarchicalGridComponent } from './hierarchical-grid/hierarchical-grid.component'; export const routes: Routes = [ { @@ -45,6 +46,12 @@ export const routes: Routes = [ pathMatch: 'full', component: GridComponent, data: { rows: 1000 } - } + }, + { + path: "hierarchical-grid-100k", + title: "Hierarchical Grid 100k records", + component: HierarchicalGridComponent, + data: { rows: 100_000 } + }, ]; diff --git a/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.html b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.html new file mode 100644 index 00000000000..42c687d2f53 --- /dev/null +++ b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.html @@ -0,0 +1,35 @@ +
+ + @for (col of columns; track col) { + + + } + + @for (col of columns; track col) { + + + } + + +
diff --git a/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.scss b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.scss new file mode 100644 index 00000000000..bc86bb4c9a3 --- /dev/null +++ b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.scss @@ -0,0 +1,10 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; +} + +.grid-wrapper { + height: 100%; + width: 100%; +} diff --git a/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.ts new file mode 100644 index 00000000000..323276a48fb --- /dev/null +++ b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.ts @@ -0,0 +1,40 @@ +import { Component, inject, ViewChild } from '@angular/core'; +import { GridColumnDataType, IGX_GRID_DIRECTIVES, IgxColumnComponent, IgxGridComponent, IgxHierarchicalGridComponent, IgxRowIslandComponent } from "igniteui-angular" +import { DataService } from '../services/data.service'; +import { ActivatedRoute } from '@angular/router'; +import { IgxRowIslandAPIService } from 'igniteui-angular/grids/hierarchical-grid/src/row-island-api.service'; + +@Component({ + selector: 'app-hierarchical-grid', + imports: [IgxHierarchicalGridComponent, IgxColumnComponent, IgxRowIslandComponent], + templateUrl: './hierarchical-grid.component.html', + styleUrl: './hierarchical-grid.component.scss' +}) +export class HierarchicalGridComponent { + protected columns: any[] = [] + protected data: any[] = []; + protected performanceDataList: PerformanceEntryList = []; + private dataService = inject(DataService); + private activatedRoute = inject(ActivatedRoute); + + @ViewChild(IgxHierarchicalGridComponent, { static: true }) + public grid: IgxHierarchicalGridComponent; + + constructor() { + this.data = this.dataService.generateHierarchicalData(this.activatedRoute.snapshot.data.rows) + this.columns = [ + { field: "Id", dataType: GridColumnDataType.Number, sortable: true, width: 'auto', groupable: true }, + { field: "Name", dataType: GridColumnDataType.String, sortable: true, width: 'auto', groupable: true }, + { field: "AthleteNumber", dataType: GridColumnDataType.Number, sortable: true, width: 'auto', groupable: true }, + { field: "Registered", dataType: GridColumnDataType.DateTime, sortable: true, width: 'auto', groupable: true }, + { field: "CountryName", dataType: GridColumnDataType.String, sortable: true, width: 'auto', groupable: true }, + { field: "FirstAppearance", dataType: GridColumnDataType.Time, sortable: true, width: 'auto', groupable: true }, + { field: "CareerStart", dataType: GridColumnDataType.Date, sortable: true, width: 'auto', groupable: true }, + { field: "Active", dataType: GridColumnDataType.Boolean, sortable: true, width: 'auto', groupable: true }, + { field: "NetWorth", dataType: GridColumnDataType.Currency, sortable: true, width: 'auto', groupable: true }, + { field: "CountryFlag", dataType: GridColumnDataType.Image, sortable: true, width: 'auto', groupable: true }, + { field: "SuccessRate", dataType: GridColumnDataType.Percent, sortable: true, width: 'auto', groupable: true }, + { field: "Position", dataType: GridColumnDataType.String, sortable: true, width: 'auto', groupable: true }, + ]; + } +} diff --git a/projects/igniteui-angular-performance/src/app/services/data.service.ts b/projects/igniteui-angular-performance/src/app/services/data.service.ts index 2cda3cbf715..b598d85a9cb 100644 --- a/projects/igniteui-angular-performance/src/app/services/data.service.ts +++ b/projects/igniteui-angular-performance/src/app/services/data.service.ts @@ -16,6 +16,12 @@ export class DataService { return data; } + public generateHierarchicalData(rows: number): any[] { + const rnd = new Mulberry32(1234); + const data = this.generateAthletesData(rnd, rows, true); + return data; + } + public generateTreeData(rows: number): any[] { const rnd = new Mulberry32(1234); const data = this.generateEmployeesData(rnd, rows); @@ -114,7 +120,7 @@ export class DataService { return currData; } - private generateAthletesData(rnd: Mulberry32, rows: number): any[] { + private generateAthletesData(rnd: Mulberry32, rows: number, children = false): any[] { const currData = []; for (let i = 0; i < rows; i++) { const rand = Math.floor(rnd.random() * Math.floor(athletesData.length)); @@ -125,6 +131,10 @@ export class DataService { dataObj["Active"] = this.randomizeBoolean(rnd); dataObj["SuccessRate"] = this.randomizePercentage(rnd); dataObj["AthleteNumber"] = this.randomizeAthleteNumber(dataObj["AthleteNumber"], rnd); + if (children) { + const rnd = new Mulberry32(i); + dataObj["childData"] = this.generateAthletesData(rnd, 5); + } currData.push(dataObj); } return currData; From d3f49c547ef148d619b973531d4006fb9e6c9a2f Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 28 Jan 2026 10:26:29 +0200 Subject: [PATCH 11/35] chore(*): Fix wrapping container in hgrid. Remove DOM read ops on recalc. --- .../directives/src/directives/for-of/for_of.directive.ts | 4 ++-- .../hierarchical-grid/src/hierarchical-grid.component.html | 2 ++ 2 files changed, 4 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 5c0287b76f1..e22a5290bf1 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 @@ -390,10 +390,10 @@ export class IgxForOfDirective extends IgxForOfToken= because `scrollTop + container size` can't be bigger than `scrollHeight`, unless something isn't updated. // Also use Math.round because Chrome has some inconsistencies and `scrollTop + container` can be float when zooming the page. - return Math.round(this.getScroll().scrollTop + this.igxForContainerSize) === scrollHeight; + return Math.round(this.scrollComponent.scrollAmount + this.igxForContainerSize) === scrollHeight; } private get _isAtBottomIndex() { 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 e7683ef7c9b..5761a4e041a 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 @@ -82,11 +82,13 @@ [igxForScrollOrientation]="'vertical'" [igxForScrollContainer]="verticalScroll" [igxForContainerSize]="calcHeight" [igxForItemSize]="renderedRowHeight" [igxForTrackBy]="trackChanges" #verticalScrollContainer (chunkPreload)="dataLoading($event)" (dataChanging)="dataRebinding($event)" (dataChanged)="dataRebound($event)"> +
+
From ffd6a830b7708e005c5935d5371853be5d3fc5d9 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 28 Jan 2026 14:21:39 +0200 Subject: [PATCH 12/35] chore(*): Set null width to skip resize on attach/detach child grids. --- .../src/app/hierarchical-grid/hierarchical-grid.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.html b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.html index 42c687d2f53..7c186c0982c 100644 --- a/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.html +++ b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.html @@ -18,7 +18,7 @@ > } - + @for (col of columns; track col) { Date: Wed, 28 Jan 2026 15:58:31 +0200 Subject: [PATCH 13/35] chore(*): Add container to size in all other grids as well. --- .../grids/pivot-grid/src/pivot-grid.component.html | 2 ++ .../grids/tree-grid/src/tree-grid.component.html | 2 ++ 2 files changed, 4 insertions(+) diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.html b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.html index 558cf1c4721..6833e93d665 100644 --- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.html +++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.html @@ -44,11 +44,13 @@ [igxForItemSize]="hasColumnLayouts ? rowHeight * multiRowLayoutRowSize + 1 : renderedRowHeight" [igxGridForOfVariableSizes]="false" #verticalScrollContainer (dataChanging)="dataRebinding($event)" (dataChanged)="dataRebound($event)"> +
+
+
+
From 278ebcb97d777b276430d81fb4ca4ffd4eaafd74 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 28 Jan 2026 16:33:06 +0200 Subject: [PATCH 14/35] chore(*): Caches only for IgxGridForOfDirective. Retain old logic for base. --- .../src/directives/for-of/for_of.directive.ts | 78 +++++++++++++------ 1 file changed, 53 insertions(+), 25 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 e22a5290bf1..ed086ff00ea 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 @@ -1,5 +1,5 @@ import { NgForOfContext } from '@angular/common'; -import { ChangeDetectorRef, ComponentRef, Directive, EmbeddedViewRef, EventEmitter, Input, IterableChanges, IterableDiffer, IterableDiffers, NgZone, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, TemplateRef, TrackByFunction, ViewContainerRef, booleanAttribute, DOCUMENT, inject, afterNextRender, runInInjectionContext, EnvironmentInjector } from '@angular/core'; +import { ChangeDetectorRef, ComponentRef, Directive, EmbeddedViewRef, EventEmitter, Input, IterableChanges, IterableDiffer, IterableDiffers, NgZone, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, TemplateRef, TrackByFunction, ViewContainerRef, booleanAttribute, DOCUMENT, inject, afterNextRender, runInInjectionContext, EnvironmentInjector, AfterViewInit } from '@angular/core'; import { DisplayContainerComponent } from './display.container'; import { HVirtualHelperComponent } from './horizontal.virtual.helper.component'; @@ -84,7 +84,7 @@ export abstract class IgxForOfToken { ], standalone: true }) -export class IgxForOfDirective extends IgxForOfToken implements OnInit, OnChanges, OnDestroy { +export class IgxForOfDirective extends IgxForOfToken implements OnInit, AfterViewInit, OnChanges, OnDestroy { private _viewContainer = inject(ViewContainerRef); protected _template = inject>>(TemplateRef); protected _differs = inject(IterableDiffers); @@ -273,8 +273,10 @@ export class IgxForOfDirective extends IgxForOfToken> = []; - protected contentResizeNotify = new Subject(); + protected contentResizeNotify = new Subject(); protected contentObserver: ResizeObserver; + protected viewObserver: ResizeObserver; + protected viewResizeNotify = new Subject(); /** Size that is being virtualized. */ protected _virtSize = 0; /** @@ -471,8 +473,9 @@ export class IgxForOfDirective extends IgxForOfToken(this.destroy$); this.contentResizeNotify.pipe( filter(() => this.igxForContainerSize && this.igxForOf && this.igxForOf.length > 0), + throttleTime(40, undefined, { leading: false, trailing: true }), destructor - ).subscribe((entries: ResizeObserverEntry[]) => this._zone.runTask(() => this.updateSizes(entries))); + ).subscribe(() => this._zone.runTask(() => this.updateSizes())); } if (this.igxForScrollOrientation === 'horizontal') { @@ -498,13 +501,23 @@ export class IgxForOfDirective extends IgxForOfToken { - // console.log(target); if (this.platformUtil.isBrowser) { - this.contentObserver = new (getResizeObserver())((entries: ResizeObserverEntry[]) => this.contentResizeNotify.next(entries)); - this.contentObserver.observe(target); + this.contentObserver = new (getResizeObserver())(() => this.contentResizeNotify.next()); + this.contentObserver.observe(this.dc.instance._viewContainer.element.nativeElement); + } + }); + } + } + + protected subscribeToViewObserver(target: Element) { + if (this.igxForScrollOrientation === 'vertical' && this.viewObserver) { + this._zone.runOutsideAngular(() => { + if (this.platformUtil.isBrowser) { + this.viewObserver = new (getResizeObserver())((entries: ResizeObserverEntry[]) => this.viewResizeNotify.next(entries)); + this.viewObserver.observe(target); } }); } @@ -830,29 +843,27 @@ export class IgxForOfDirective extends IgxForOfToken 0) { - // console.log('RECALC Size increased for index ', targetIndex, ' from ', oldVal, ' to ', newVal); - // } diffs.push(currDiff); totalDiff += currDiff; - this.individualSizeCache[targetIndex] = cachedNodeSize; + this.individualSizeCache[targetIndex] = nodeSize; this.sizesCache[targetIndex + 1] = (this.sizesCache[targetIndex] || 0) + newVal; } - - // console.log(this.individualSizeCache); // update cache if (Math.abs(totalDiff) > 0) { for (let j = this.state.startIndex + this.state.chunkSize + 1; j < this.sizesCache.length; j++) { @@ -963,14 +974,25 @@ export class IgxForOfDirective extends IgxForOfToken { const index = parseInt(entry.target.getAttribute('data-index'), 0); const height = entry.contentRect.height; const embView = this._embeddedViews[index - this.state.startIndex]; this._embeddedViewSizesCache.set(embView, height); - // console.log('Observed size change for index ', index, ' new size ', height); - // console.log(this._embeddedViewSizesCache); }); this.recalcUpdateSizes(); } @@ -1418,7 +1440,7 @@ export class IgxForOfDirective extends IgxForOfToken node.nodeType === Node.ELEMENT_NODE) || oldElem.rootNodes[0].nextElementSibling); + this.viewObserver?.unobserve(oldElem.rootNodes.find(node => node.nodeType === Node.ELEMENT_NODE) || oldElem.rootNodes[0].nextElementSibling); // also detach from ViewContainerRef to make absolutely sure this is removed from the view container. this.dc.instance._vcr.detach(this.dc.instance._vcr.length - 1); oldElem.destroy(); @@ -1632,6 +1654,12 @@ export class IgxGridForOfDirective extends IgxForOfDirec this.syncService.setMaster(this); super.ngOnInit(); this.removeScrollEventListeners(); + const destructor = takeUntil(this.destroy$); + this.viewObserver = new (getResizeObserver())((entries: ResizeObserverEntry[]) => this.viewResizeNotify.next(entries)); + this.viewResizeNotify.pipe( + filter(() => this.igxForContainerSize && this.igxForOf && this.igxForOf.length > 0), + destructor + ).subscribe((entries: ResizeObserverEntry[]) => this._zone.runTask(() => this.updateViewSizes(entries))); } public override ngOnChanges(changes: SimpleChanges) { @@ -1857,7 +1885,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec ); this._embeddedViews.push(embeddedView); - this.subscribeToObserver(embeddedView.rootNodes.find(node => node.nodeType === Node.ELEMENT_NODE) || embeddedView.rootNodes[0].nextElementSibling); + this.subscribeToViewObserver(embeddedView.rootNodes.find(node => node.nodeType === Node.ELEMENT_NODE) || embeddedView.rootNodes[0].nextElementSibling); this.state.chunkSize++; } From 9b8c5adf63d2ab78f238e06dedddef621370def3 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 29 Jan 2026 13:45:14 +0200 Subject: [PATCH 15/35] chore(*): Fix timing so that tests do not disconnect. --- projects/igniteui-angular/grids/grid/src/cell.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/grids/grid/src/cell.spec.ts b/projects/igniteui-angular/grids/grid/src/cell.spec.ts index 95d97bc6142..23ef82c4266 100644 --- a/projects/igniteui-angular/grids/grid/src/cell.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/cell.spec.ts @@ -185,14 +185,14 @@ describe('IgxGrid - Cell component #grid', () => { it('should fit last cell in the available display container when there is vertical and horizontal scroll.', (async () => { fix.componentInstance.columns = fix.componentInstance.generateCols(100); fix.componentInstance.data = fix.componentInstance.generateData(1000); - await wait(); + await wait(16); fix.detectChanges(); const firsCell = GridFunctions.getRowCells(fix, 1)[0]; expect(GridFunctions.getValueFromCellElement(firsCell)).toEqual('0'); fix.componentInstance.scrollLeft(999999); - await wait(); + await wait(16); // This won't work always in debugging mode due to the angular native events behavior, so errors are expected fix.detectChanges(); const cells = GridFunctions.getRowCells(fix, 1); @@ -238,7 +238,7 @@ describe('IgxGrid - Cell component #grid', () => { const scrollbar = grid.headerContainer.getScroll(); scrollbar.scrollLeft = 10000; fix.detectChanges(); - await wait(); + await wait(16); const lastColumnCells = grid.columnList.get(grid.columnList.length - 1).cells; fix.detectChanges(); lastColumnCells.forEach((item) => { From af81bb01cfb58c3ebe0c7f9b43e43e0f7b4ca418 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 29 Jan 2026 16:11:24 +0200 Subject: [PATCH 16/35] chore(*): Remove zone run since it's unnecessary. --- .../directives/src/directives/for-of/for_of.directive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ed086ff00ea..7e325372ba5 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 @@ -1659,7 +1659,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec this.viewResizeNotify.pipe( filter(() => this.igxForContainerSize && this.igxForOf && this.igxForOf.length > 0), destructor - ).subscribe((entries: ResizeObserverEntry[]) => this._zone.runTask(() => this.updateViewSizes(entries))); + ).subscribe((entries: ResizeObserverEntry[]) => () => this.updateViewSizes(entries)); } public override ngOnChanges(changes: SimpleChanges) { From 06337249d41d72b64edc4ab242d8c53540168dd7 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 29 Jan 2026 16:33:14 +0200 Subject: [PATCH 17/35] chore(*): Check if the await causes a disconnect in tests. --- projects/igniteui-angular/grids/grid/src/cell.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/grids/grid/src/cell.spec.ts b/projects/igniteui-angular/grids/grid/src/cell.spec.ts index 23ef82c4266..481596d92f2 100644 --- a/projects/igniteui-angular/grids/grid/src/cell.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/cell.spec.ts @@ -192,7 +192,7 @@ describe('IgxGrid - Cell component #grid', () => { expect(GridFunctions.getValueFromCellElement(firsCell)).toEqual('0'); fix.componentInstance.scrollLeft(999999); - await wait(16); + // This won't work always in debugging mode due to the angular native events behavior, so errors are expected fix.detectChanges(); const cells = GridFunctions.getRowCells(fix, 1); From 379aac52129c91c929cf11d65ce8ff8bb257964a Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 29 Jan 2026 19:29:05 +0200 Subject: [PATCH 18/35] chore(*): Update tests according to new structure. --- .../grids/grid/src/grid.master-detail.spec.ts | 12 ++++++------ .../test-utils/grid-functions.spec.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts b/projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts index 497caf776ae..ab2dd988837 100644 --- a/projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts @@ -479,7 +479,7 @@ describe('IgxGrid Master Detail #grid', () => { await wait(DEBOUNCE_TIME); fix.detectChanges(); - const detailRow = row.nativeElement.previousElementSibling as HTMLElement; + const detailRow = row.nativeElement.parentElement.previousElementSibling.children[0] as HTMLElement; GridFunctions.verifyMasterDetailRowFocused(detailRow); expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy(); }); @@ -499,7 +499,7 @@ describe('IgxGrid Master Detail #grid', () => { fix.detectChanges(); row = grid.gridAPI.get_row_by_index(2); - const detailRow = row.nativeElement.previousElementSibling as HTMLElement; + const detailRow = row.nativeElement.parentElement.previousElementSibling.children[0] as HTMLElement; GridFunctions.verifyMasterDetailRowFocused(detailRow); expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy(); }); @@ -1204,7 +1204,7 @@ describe('IgxGrid Master Detail #grid', () => { await wait(); fix.detectChanges(); - const allRows = grid.tbody.nativeElement.firstElementChild.children; + const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]) expect(allRows.length).toBe(8); expect(allRows[0].tagName.toLowerCase()).toBe(GROUP_ROW_TAG); expect(allRows[1].tagName.toLowerCase()).toBe(ROW_TAG); @@ -1227,7 +1227,7 @@ describe('IgxGrid Master Detail #grid', () => { grid.summaryPosition = GridSummaryPosition.top; fix.detectChanges(); - const allRows = grid.tbody.nativeElement.firstElementChild.children; + const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]) expect(allRows.length).toBe(8); expect(allRows[0].tagName.toLowerCase()).toBe(GROUP_ROW_TAG); expect(allRows[1].tagName.toLowerCase()).toBe(SUMMARY_ROW_TAG); @@ -1245,7 +1245,7 @@ describe('IgxGrid Master Detail #grid', () => { after grouping by and detail views for the group rows are collapsed.`, () => { grid.summaryPosition = GridSummaryPosition.top; fix.detectChanges(); - const allRows = grid.tbody.nativeElement.firstElementChild.children; + const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]) expect(allRows.length).toBe(9); expect(allRows[0].tagName.toLowerCase()).toBe(GROUP_ROW_TAG); expect(allRows[1].tagName.toLowerCase()).toBe(SUMMARY_ROW_TAG); @@ -1260,7 +1260,7 @@ describe('IgxGrid Master Detail #grid', () => { it(`Should correctly position summary rows when summary row position is bottom after grouping by and detail views for the group rows are collapsed.`, () => { - const allRows = grid.tbody.nativeElement.firstElementChild.children; + const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]) expect(allRows.length).toBe(9); expect(allRows[0].tagName.toLowerCase()).toBe(GROUP_ROW_TAG); expect(allRows[1].tagName.toLowerCase()).toBe(ROW_TAG); diff --git a/projects/igniteui-angular/test-utils/grid-functions.spec.ts b/projects/igniteui-angular/test-utils/grid-functions.spec.ts index a46159be685..bf0b8125a4d 100644 --- a/projects/igniteui-angular/test-utils/grid-functions.spec.ts +++ b/projects/igniteui-angular/test-utils/grid-functions.spec.ts @@ -208,7 +208,7 @@ export class GridFunctions { } public static getMasterRowDetail(row) { - const nextSibling = row.element.nativeElement.nextElementSibling; + const nextSibling = row.element.nativeElement.parentElement.nextElementSibling.children[0]; if (nextSibling && nextSibling.tagName.toLowerCase() === 'div' && nextSibling.getAttribute('detail') === 'true') { @@ -252,8 +252,8 @@ export class GridFunctions { public static getMasterRowDetailDebug(fix: ComponentFixture, row: IgxRowDirective) { const rowDE = fix.debugElement.queryAll(By.directive(IgxRowDirective)).find(el => el.componentInstance === row); - const detailDE = rowDE.parent.children - .find(el => el.attributes['detail'] === 'true' && el.attributes['data-rowindex'] === row.index + 1 + ''); + const detailDE = rowDE.parent.parent.children + .find(el => el.children[0].attributes['detail'] === 'true' && el.children[0].attributes['data-rowindex'] === row.index + 1 + ''); return detailDE; } From e9192e0073864dace8e302373d51bcbed0431365 Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 30 Jan 2026 10:24:05 +0200 Subject: [PATCH 19/35] chore(*): Remove unnecessary size recalc during scroll position update. --- .../directives/src/directives/for-of/for_of.directive.ts | 1 - 1 file changed, 1 deletion(-) 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 7e325372ba5..572ea83cd2b 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 @@ -1526,7 +1526,6 @@ export class IgxForOfDirective extends IgxForOfToken 0 && this.scrollPosition > 0) { - this.recalcUpdateSizes(); const offset = this.igxForScrollOrientation === 'horizontal' ? parseInt(this.dc.instance._viewContainer.element.nativeElement.style.left, 10) : Number(this.dc.instance._viewContainer.element.nativeElement.style.transform?.match(/translateY\((-?\d+\.?\d*)px\)/)?.[1]); From ab14780771afcd55d5e83daeb7f89664355e1c1a Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 30 Jan 2026 11:59:38 +0200 Subject: [PATCH 20/35] chore(*): Update groupBy tests according to new DOM structure. --- .../grids/grid/src/grid.groupby.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/igniteui-angular/grids/grid/src/grid.groupby.spec.ts b/projects/igniteui-angular/grids/grid/src/grid.groupby.spec.ts index 0087c32f4db..04c4918e841 100644 --- a/projects/igniteui-angular/grids/grid/src/grid.groupby.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid.groupby.spec.ts @@ -167,11 +167,11 @@ describe('IgxGrid - GroupBy #grid', () => { expect(groupRows.length).toEqual(4); expect(dataRows.length).toEqual(8); - const expectedValue1 = groupRows[1].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent; + const expectedValue1 = groupRows[1].nativeElement.parentElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent; const actualValue1 = groupRows[1].element.nativeElement.querySelector('.igx-group-label__text').textContent; - const expectedValue2 = groupRows[2].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent; + const expectedValue2 = groupRows[2].nativeElement.parentElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent; const actualValue2 = groupRows[2].element.nativeElement.querySelector('.igx-group-label__text').textContent; - const expectedValue3 = groupRows[3].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent; + const expectedValue3 = groupRows[3].nativeElement.parentElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent; const actualValue3 = groupRows[3].element.nativeElement.querySelector('.igx-group-label__text').textContent; expect(actualValue1).toEqual(expectedValue1); @@ -300,7 +300,7 @@ describe('IgxGrid - GroupBy #grid', () => { fix.detectChanges(); const groupRows = grid.groupsRowList.toArray(); - const expectedValue1 = groupRows[0].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent; + const expectedValue1 = groupRows[0].nativeElement.parentElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent; const actualValue1 = groupRows[0].element.nativeElement.querySelector('.igx-group-label__text').textContent; expect(expectedValue1).toEqual(actualValue1); })); @@ -317,7 +317,7 @@ describe('IgxGrid - GroupBy #grid', () => { fix.detectChanges(); const groupRows = grid.groupsRowList.toArray(); - const expectedValue1 = groupRows[0].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[4].textContent; + const expectedValue1 = groupRows[0].nativeElement.parentElement.nextElementSibling.querySelectorAll('igx-grid-cell')[4].textContent; const actualValue1 = groupRows[0].element.nativeElement.querySelector('.igx-group-label__text').textContent; expect(expectedValue1).toEqual(actualValue1); })); From 100d9043b12019ec981300379986646e4d5647ca Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 30 Jan 2026 13:17:56 +0200 Subject: [PATCH 21/35] chore(*): Update test timing. --- projects/igniteui-angular/grids/grid/src/cell.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/grids/grid/src/cell.spec.ts b/projects/igniteui-angular/grids/grid/src/cell.spec.ts index 481596d92f2..e3a0a9f5939 100644 --- a/projects/igniteui-angular/grids/grid/src/cell.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/cell.spec.ts @@ -185,13 +185,17 @@ describe('IgxGrid - Cell component #grid', () => { it('should fit last cell in the available display container when there is vertical and horizontal scroll.', (async () => { fix.componentInstance.columns = fix.componentInstance.generateCols(100); fix.componentInstance.data = fix.componentInstance.generateData(1000); + fix.detectChanges(); await wait(16); fix.detectChanges(); const firsCell = GridFunctions.getRowCells(fix, 1)[0]; expect(GridFunctions.getValueFromCellElement(firsCell)).toEqual('0'); - fix.componentInstance.scrollLeft(999999); + const scrollbar = grid.headerContainer.getScroll(); + scrollbar.scrollLeft = 999999; + + await wait(16); // This won't work always in debugging mode due to the angular native events behavior, so errors are expected fix.detectChanges(); From 6aeb4fe132934dc266459abbccba4f9bcb88fc36 Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 30 Jan 2026 13:28:25 +0200 Subject: [PATCH 22/35] chore(*): Clean up imports and fix formatting. --- .../app/hierarchical-grid/hierarchical-grid.component.ts | 3 +-- .../grids/grid/src/grid.master-detail.spec.ts | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.ts index 323276a48fb..a23f8a5bfa0 100644 --- a/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular-performance/src/app/hierarchical-grid/hierarchical-grid.component.ts @@ -1,8 +1,7 @@ import { Component, inject, ViewChild } from '@angular/core'; -import { GridColumnDataType, IGX_GRID_DIRECTIVES, IgxColumnComponent, IgxGridComponent, IgxHierarchicalGridComponent, IgxRowIslandComponent } from "igniteui-angular" +import { GridColumnDataType, IgxColumnComponent, IgxHierarchicalGridComponent, IgxRowIslandComponent } from "igniteui-angular" import { DataService } from '../services/data.service'; import { ActivatedRoute } from '@angular/router'; -import { IgxRowIslandAPIService } from 'igniteui-angular/grids/hierarchical-grid/src/row-island-api.service'; @Component({ selector: 'app-hierarchical-grid', diff --git a/projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts b/projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts index ab2dd988837..95763354bd1 100644 --- a/projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts @@ -1204,7 +1204,7 @@ describe('IgxGrid Master Detail #grid', () => { await wait(); fix.detectChanges(); - const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]) + const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]); expect(allRows.length).toBe(8); expect(allRows[0].tagName.toLowerCase()).toBe(GROUP_ROW_TAG); expect(allRows[1].tagName.toLowerCase()).toBe(ROW_TAG); @@ -1227,7 +1227,7 @@ describe('IgxGrid Master Detail #grid', () => { grid.summaryPosition = GridSummaryPosition.top; fix.detectChanges(); - const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]) + const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]); expect(allRows.length).toBe(8); expect(allRows[0].tagName.toLowerCase()).toBe(GROUP_ROW_TAG); expect(allRows[1].tagName.toLowerCase()).toBe(SUMMARY_ROW_TAG); @@ -1245,7 +1245,7 @@ describe('IgxGrid Master Detail #grid', () => { after grouping by and detail views for the group rows are collapsed.`, () => { grid.summaryPosition = GridSummaryPosition.top; fix.detectChanges(); - const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]) + const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]); expect(allRows.length).toBe(9); expect(allRows[0].tagName.toLowerCase()).toBe(GROUP_ROW_TAG); expect(allRows[1].tagName.toLowerCase()).toBe(SUMMARY_ROW_TAG); @@ -1260,7 +1260,7 @@ describe('IgxGrid Master Detail #grid', () => { it(`Should correctly position summary rows when summary row position is bottom after grouping by and detail views for the group rows are collapsed.`, () => { - const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]) + const allRows = [...grid.tbody.nativeElement.firstElementChild.children].map(x=> x.children[0]); expect(allRows.length).toBe(9); expect(allRows[0].tagName.toLowerCase()).toBe(GROUP_ROW_TAG); expect(allRows[1].tagName.toLowerCase()).toBe(ROW_TAG); From 08b034fd7443feb3c1cf8602a5aeb6d385883697 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 10:09:29 +0200 Subject: [PATCH 23/35] chore(*): Fix flicker in test. --- projects/igniteui-angular/grids/grid/src/grid.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/grids/grid/src/grid.component.spec.ts b/projects/igniteui-angular/grids/grid/src/grid.component.spec.ts index 2db66e08b36..9f5c2212375 100644 --- a/projects/igniteui-angular/grids/grid/src/grid.component.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid.component.spec.ts @@ -2029,7 +2029,7 @@ describe('IgxGrid Component Tests #grid', () => { grid.navigateTo(50, 16); fix.detectChanges(); - await wait(60); + await wait(100); fix.detectChanges(); expect(headerRowElement.getAttribute('aria-rowindex')).toBe('1'); From 14276fbf3e52a19d94a026f53c74d1470abbea77 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 10:25:49 +0200 Subject: [PATCH 24/35] chore(*): Remove unnecessary ResizeObserver init in vertical scroll. --- .../directives/src/directives/for-of/for_of.directive.ts | 1 - 1 file changed, 1 deletion(-) 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 572ea83cd2b..f9e0a8dde44 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 @@ -516,7 +516,6 @@ export class IgxForOfDirective extends IgxForOfToken { if (this.platformUtil.isBrowser) { - this.viewObserver = new (getResizeObserver())((entries: ResizeObserverEntry[]) => this.viewResizeNotify.next(entries)); this.viewObserver.observe(target); } }); From 891edaa16b88b8dbe0ece2ef45b1a716fa60b0df Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 11:03:52 +0200 Subject: [PATCH 25/35] chore(*): Clear view sizes cache and disconnect view observer on destroy --- .../directives/src/directives/for-of/for_of.directive.ts | 5 +++++ 1 file changed, 5 insertions(+) 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 f9e0a8dde44..53dba6f082f 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 @@ -532,6 +532,11 @@ export class IgxForOfDirective extends IgxForOfToken Date: Mon, 2 Feb 2026 12:10:06 +0200 Subject: [PATCH 26/35] chore(*): Add back zone run since otherwise observer does not trigger. --- .../directives/src/directives/for-of/for_of.directive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 53dba6f082f..b40a4ffa159 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 @@ -1662,7 +1662,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec this.viewResizeNotify.pipe( filter(() => this.igxForContainerSize && this.igxForOf && this.igxForOf.length > 0), destructor - ).subscribe((entries: ResizeObserverEntry[]) => () => this.updateViewSizes(entries)); + ).subscribe((entries: ResizeObserverEntry[]) => this._zone.runTask(() => this.updateViewSizes(entries))); } public override ngOnChanges(changes: SimpleChanges) { From 8b2fa8ed9ec843fc212259c36e0dc46959db7b3b Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 15:29:45 +0200 Subject: [PATCH 27/35] chore(*): Remove fake test zone, to prevent disconnects. --- projects/igniteui-angular/grids/grid/src/cell.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/grids/grid/src/cell.spec.ts b/projects/igniteui-angular/grids/grid/src/cell.spec.ts index e3a0a9f5939..63143376997 100644 --- a/projects/igniteui-angular/grids/grid/src/cell.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/cell.spec.ts @@ -156,7 +156,6 @@ describe('IgxGrid - Cell component #grid', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [NoopAnimationsModule, VirtualGridComponent], - providers: [{ provide: NgZone, useFactory: () => new TestNgZone() }] }).compileComponents(); })); From 4902da5ad6272223d1e49684244950df9c5cde89 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 15:54:13 +0200 Subject: [PATCH 28/35] chore(*): Refactor how size is retrieved for base/ IgxGridForOfDirective. --- .../src/directives/for-of/for_of.directive.ts | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 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 b40a4ffa159..0471fc49986 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 @@ -95,7 +95,7 @@ export class IgxForOfDirective extends IgxForOfToken, number>(); + protected _embeddedViewSizesCache = new Map, number>(); /** * Sets the data to be rendered. @@ -410,7 +410,7 @@ export class IgxForOfDirective extends IgxForOfToken parseInt(this.igxForContainerSize, 10); } - private get embeddedViewNodes() { + protected get embeddedViewNodes() { const result = new Array(this._embeddedViews.length); for (let i = 0; i < this._embeddedViews.length; i++) { const view = this._embeddedViews[i]; @@ -837,6 +837,16 @@ export class IgxForOfDirective extends IgxForOfToken 5; } + protected getEmbeddedViewSize(index:number ) { + const dimension = this.igxForScrollOrientation === 'horizontal' ? + this.igxForSizePropName : 'height'; + const rNode = this.embeddedViewNodes[index]; + const nodeSize = dimension === 'height' ? + rNode.clientHeight + this.getMargin(rNode, dimension): + rNode.clientWidth + this.getMargin(rNode, dimension); + return nodeSize; + } + /** * @hidden @@ -847,26 +857,19 @@ export class IgxForOfDirective extends IgxForOfToken 0) { @@ -1541,7 +1544,7 @@ export class IgxForOfDirective extends IgxForOfToken extends IgxForOfDirec return totalSize; } + protected override getEmbeddedViewSize(index:number ) { + if (this.igxForScrollOrientation === 'vertical') { + const view = this._embeddedViews[index]; + return this._embeddedViewSizesCache.get(view) || parseInt(this.igxForItemSize, 10); + } else { + return super.getEmbeddedViewSize(index); + } + } + protected override _updateSizeCache(changes: IterableChanges = null) { const oldSize = this.individualSizeCache.length > 0 ? this.individualSizeCache.reduce((acc, val) => acc + val) : 0; let newSize = oldSize; From e28d09c6ba774e78727e94578c37d1ce63e3e33b Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 16:17:04 +0200 Subject: [PATCH 29/35] test(*): Prevent ResizeObserver loop in tests. --- .../src/hierarchical-grid.navigation.spec.ts | 4 ++-- .../grids/tree-grid/src/tree-grid-summaries.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts index 45a1b87d69a..e8252264f02 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts @@ -40,7 +40,7 @@ describe('IgxHierarchicalGrid Navigation', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - providers: [{ provide: SCROLL_THROTTLE_TIME, useValue: 0 }] + providers: [{ provide: SCROLL_THROTTLE_TIME, useValue: 1 }] }); })); @@ -50,7 +50,7 @@ describe('IgxHierarchicalGrid Navigation', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - providers: [{ provide: SCROLL_THROTTLE_TIME, useValue: 0 }] + providers: [{ provide: SCROLL_THROTTLE_TIME, useValue: 1 }] }); fixture = TestBed.createComponent(IgxHierarchicalGridTestBaseComponent); fixture.detectChanges(); diff --git a/projects/igniteui-angular/grids/tree-grid/src/tree-grid-summaries.spec.ts b/projects/igniteui-angular/grids/tree-grid/src/tree-grid-summaries.spec.ts index b27b063dbd7..354169ee337 100644 --- a/projects/igniteui-angular/grids/tree-grid/src/tree-grid-summaries.spec.ts +++ b/projects/igniteui-angular/grids/tree-grid/src/tree-grid-summaries.spec.ts @@ -36,7 +36,7 @@ describe('IgxTreeGrid - Summaries #tGrid', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - providers: [{ provide: SCROLL_THROTTLE_TIME, useValue: 0 }] + providers: [{ provide: SCROLL_THROTTLE_TIME, useValue: 1 }] }); })); From cf021bae53cb9774dd84952814046ee8a82d15f8 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 16:22:44 +0200 Subject: [PATCH 30/35] test(grid): Configure SCROLL_THROTTLE_TIME for summaries tests. --- projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts b/projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts index 923ab8e26fd..92d52ddc952 100644 --- a/projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts @@ -18,6 +18,7 @@ import { DropPosition, IgxColumnComponent, IgxDateSummaryOperand, IgxGridRow, Ig import { DatePipe } from '@angular/common'; import { IgxGridGroupByRowComponent } from './groupby-row.component'; import { GridSummaryCalculationMode, IColumnPipeArgs, IgxNumberFilteringOperand, IgxStringFilteringOperand, IgxSummaryResult, SortingDirection } from 'igniteui-angular/core'; +import { SCROLL_THROTTLE_TIME } from './../../grid/src/grid-base.directive'; describe('IgxGrid - Summaries #grid', () => { @@ -1204,6 +1205,9 @@ describe('IgxGrid - Summaries #grid', () => { let fix; let grid; beforeEach(() => { + TestBed.configureTestingModule({ + providers: [{ provide: SCROLL_THROTTLE_TIME, useValue: 0 }] + }); fix = TestBed.createComponent(SummariesGroupByTransactionsComponent); fix.detectChanges(); grid = fix.componentInstance.grid; From f7ad651dd4b8768f0ef6513858a0beffc2d07054 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 16:35:34 +0200 Subject: [PATCH 31/35] chore(*): Fix test timings. --- projects/igniteui-angular/grids/grid/src/cell-merge.spec.ts | 5 +++-- .../src/hierarchical-grid.navigation.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/grids/grid/src/cell-merge.spec.ts b/projects/igniteui-angular/grids/grid/src/cell-merge.spec.ts index 918975267a2..d4bea0e8436 100644 --- a/projects/igniteui-angular/grids/grid/src/cell-merge.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/cell-merge.spec.ts @@ -186,11 +186,12 @@ describe('IgxGrid - Cell merging #grid', () => { hasClass(mergedCell, 'igx-grid__td--merged-hovered', true); }); - it('should set correct size to merged cell that spans multiple rows that have different sizes.', () => { + it('should set correct size to merged cell that spans multiple rows that have different sizes.', async() => { const col = grid.getColumnByName('ID'); col.bodyTemplate = fix.componentInstance.customTemplate; fix.detectChanges(); - grid.verticalScrollContainer.recalcUpdateSizes(); + await wait(100); + fix.detectChanges(); grid.dataRowList.toArray().forEach(x => x.cdr.detectChanges()); const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; // one row is 100px, other is 200, 2px border diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts index e8252264f02..96edde41064 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts @@ -172,13 +172,13 @@ describe('IgxHierarchicalGrid Navigation', () => { it('should allow navigating to start in child grid when child grid target row moves outside the parent view port.', async () => { hierarchicalGrid.verticalScrollContainer.scrollTo(2); fixture.detectChanges(); - await wait(); + await wait(DEBOUNCE_TIME); const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0]; const horizontalScrDir = childGrid.dataRowList.toArray()[0].virtDirRow; horizontalScrDir.scrollTo(6); fixture.detectChanges(); - await wait(); + await wait(DEBOUNCE_TIME); const childLastCell = childGrid.dataRowList.toArray()[9].cells.toArray()[3]; GridFunctions.focusCell(fixture, childLastCell); From 4e08bf2e275747a36ef7aaaa9ff308cb4993b87c Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 2 Feb 2026 17:17:20 +0200 Subject: [PATCH 32/35] chore(*): Increase wait times in summaries tests for stability --- .../grids/grid/src/grid-summary.spec.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts b/projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts index 92d52ddc952..e251cbda1ef 100644 --- a/projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid-summary.spec.ts @@ -1501,6 +1501,8 @@ describe('IgxGrid - Summaries #grid', () => { fieldName: 'ParentID', dir: SortingDirection.Asc, ignoreCase: false }); fix.detectChanges(); + await wait(60); + const newRow = { ID: 777, @@ -1512,6 +1514,7 @@ describe('IgxGrid - Summaries #grid', () => { }; grid.addRow(newRow); fix.detectChanges(); + await wait(60); const summaryRow = GridSummaryFunctions.getRootSummaryRow(fix); GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['9']); @@ -1520,7 +1523,7 @@ describe('IgxGrid - Summaries #grid', () => { GridSummaryFunctions.verifyColumnSummaries(summaryRow, 4, ['Min', 'Max'], ['19', '50']); grid.verticalScrollContainer.scrollTo(grid.dataView.length - 1); - await wait(50); + await wait(60); fix.detectChanges(); let row = grid.gridAPI.get_row_by_index(16); @@ -1532,7 +1535,7 @@ describe('IgxGrid - Summaries #grid', () => { // Undo transactions grid.transactions.undo(); - await wait(50); + await wait(60); fix.detectChanges(); row = grid.gridAPI.get_row_by_index(16); expect(row).toBeUndefined(); @@ -1541,7 +1544,7 @@ describe('IgxGrid - Summaries #grid', () => { // redo transactions grid.transactions.redo(); - await wait(50); + await wait(60); fix.detectChanges(); row = grid.gridAPI.get_row_by_index(16); @@ -1550,13 +1553,13 @@ describe('IgxGrid - Summaries #grid', () => { GridSummaryFunctions.verifyColumnSummariesBySummaryRowIndex(fix, 0, 5, ['Count'], ['9']); grid.verticalScrollContainer.scrollTo(grid.dataView.length - 1); - await wait(50); + await wait(60); fix.detectChanges(); GridSummaryFunctions.verifyColumnSummariesBySummaryRowIndex(fix, 18, 5, ['Count'], ['1']); // Discard grid.transactions.clear(); - await wait(50); + await wait(60); fix.detectChanges(); row = grid.gridAPI.get_row_by_index(16); From c3732484cbaa64188d7f7d473bad7014d1f0872e Mon Sep 17 00:00:00 2001 From: MKirova Date: Tue, 3 Feb 2026 18:58:03 +0200 Subject: [PATCH 33/35] chore(*): Apply review comments. --- .../src/directives/for-of/for_of.directive.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 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 0471fc49986..57f2015b12b 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 @@ -95,7 +95,7 @@ export class IgxForOfDirective extends IgxForOfToken, number>(); + protected _embeddedViewSizesCache = new WeakMap, number>(); /** * Sets the data to be rendered. @@ -534,7 +534,6 @@ export class IgxForOfDirective extends IgxForOfToken extends IgxForOfToken 5; } - protected getEmbeddedViewSize(index:number ) { + protected getNodeSize(rNode: Element, _index: number): number { const dimension = this.igxForScrollOrientation === 'horizontal' ? this.igxForSizePropName : 'height'; - const rNode = this.embeddedViewNodes[index]; const nodeSize = dimension === 'height' ? rNode.clientHeight + this.getMargin(rNode, dimension): rNode.clientWidth + this.getMargin(rNode, dimension); @@ -860,10 +858,10 @@ export class IgxForOfDirective extends IgxForOfToken extends IgxForOfToken acc + val; - const hSum = this.individualSizeCache.reduce(reducer); - if (hSum > this._maxSize) { - this._virtRatio = hSum / this._maxSize; + this._virtSize += totalDiff; + if (this._virtSize > this._maxSize) { + this._virtRatio = this._virtSize / this._maxSize; } this.scrollComponent.size = Math.min(this.scrollComponent.size + totalDiff, this._maxSize); - this._virtSize = hSum; + if (!this.scrollComponent.destroyed) { this.scrollComponent.cdr.detectChanges(); } @@ -995,12 +993,12 @@ export class IgxForOfDirective extends IgxForOfToken { - const index = parseInt(entry.target.getAttribute('data-index'), 0); + for (const entry of entries) { + const index = parseInt(entry.target.getAttribute('data-index'), 10); const height = entry.contentRect.height; const embView = this._embeddedViews[index - this.state.startIndex]; this._embeddedViewSizesCache.set(embView, height); - }); + } this.recalcUpdateSizes(); } @@ -1822,12 +1820,12 @@ export class IgxGridForOfDirective extends IgxForOfDirec return totalSize; } - protected override getEmbeddedViewSize(index:number ) { + protected override getNodeSize(rNode: Element, index?: number): number { if (this.igxForScrollOrientation === 'vertical') { const view = this._embeddedViews[index]; return this._embeddedViewSizesCache.get(view) || parseInt(this.igxForItemSize, 10); } else { - return super.getEmbeddedViewSize(index); + return super.getNodeSize(rNode, index); } } From 21cfb85c9e8c3dd0c78bb8cf2b34e6073070b063 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 4 Feb 2026 11:08:44 +0200 Subject: [PATCH 34/35] chore(*): Add null check. --- .../directives/src/directives/for-of/for_of.directive.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 57f2015b12b..9922cfe9588 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 @@ -997,7 +997,9 @@ export class IgxForOfDirective extends IgxForOfToken Date: Wed, 11 Feb 2026 16:03:31 +0200 Subject: [PATCH 35/35] chore(*): Fix timing in tests. --- .../grid.multi-row-layout.integration.spec.ts | 1 + .../src/hierarchical-grid.navigation.spec.ts | 37 ++++++++++--------- .../tree-grid/src/tree-grid-summaries.spec.ts | 1 + 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/projects/igniteui-angular/grids/grid/src/grid.multi-row-layout.integration.spec.ts b/projects/igniteui-angular/grids/grid/src/grid.multi-row-layout.integration.spec.ts index 12692d5eda7..018f54e75fa 100644 --- a/projects/igniteui-angular/grids/grid/src/grid.multi-row-layout.integration.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid.multi-row-layout.integration.spec.ts @@ -850,6 +850,7 @@ describe('IgxGrid - multi-row-layout Integration #grid - ', () => { strategy: DefaultSortingStrategy.instance() }); fixture.detectChanges(); + await wait(16); expect(grid.rowList.length).toEqual(8); expect((grid.verticalScrollContainer.getScroll().children[0] as HTMLElement).offsetHeight - diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts index e362242f7c8..c96232939e5 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.navigation.spec.ts @@ -979,7 +979,7 @@ describe('IgxHierarchicalGrid Navigation', () => { clearGridSubs(); }); - it('should navigate to exact child grid with navigateToChildGrid.', (done) => { + it('should navigate to exact child grid with navigateToChildGrid.', async() => { hierarchicalGrid.primaryKey = 'ID'; hierarchicalGrid.expandChildren = false; fixture.detectChanges(); @@ -988,23 +988,25 @@ describe('IgxHierarchicalGrid Navigation', () => { rowIslandKey: 'childData2', rowID: 10 }; - hierarchicalGrid.navigation.navigateToChildGrid([path], () => { - fixture.detectChanges(); - const childGrid = hierarchicalGrid.gridAPI.getChildGrid([path]).nativeElement; - expect(childGrid).not.toBe(undefined); - - const parentBottom = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect().bottom; - const parentTop = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect().top; - // check it's in view within its parent - expect(childGrid.getBoundingClientRect().bottom <= parentBottom && childGrid.getBoundingClientRect().top >= parentTop); - done(); - }); + await wait(16); + hierarchicalGrid.navigation.navigateToChildGrid([path]); + await wait(DEBOUNCE_TIME); + fixture.detectChanges(); + const childGrid = hierarchicalGrid.gridAPI.getChildGrid([path]).nativeElement; + expect(childGrid).not.toBe(undefined); + + const parentBottom = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect().bottom; + const parentTop = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect().top; + // check it's in view within its parent + expect(childGrid.getBoundingClientRect().bottom <= parentBottom && childGrid.getBoundingClientRect().top >= parentTop); }); - it('should navigate to exact nested child grid with navigateToChildGrid.', (done) => { + it('should navigate to exact nested child grid with navigateToChildGrid.', async() => { hierarchicalGrid.expandChildren = false; + await wait(DEBOUNCE_TIME); hierarchicalGrid.primaryKey = 'ID'; hierarchicalGrid.childLayoutList.toArray()[0].primaryKey = 'ID'; fixture.detectChanges(); + await wait(DEBOUNCE_TIME); const targetRoot: IPathSegment = { rowKey: 10, rowIslandKey: 'childData', @@ -1016,19 +1018,18 @@ describe('IgxHierarchicalGrid Navigation', () => { rowID: 5 }; - hierarchicalGrid.navigation.navigateToChildGrid([targetRoot, targetNested], () => { + hierarchicalGrid.navigation.navigateToChildGrid([targetRoot, targetNested]); + await wait(DEBOUNCE_TIME * 2); fixture.detectChanges(); - const childGrid = hierarchicalGrid.gridAPI.getChildGrid([targetRoot]).nativeElement; + const childGrid = hierarchicalGrid.gridAPI.getChildGrid([targetRoot]).nativeElement; expect(childGrid).not.toBe(undefined); - const childGridNested = hierarchicalGrid.gridAPI.getChildGrid([targetRoot, targetNested]).nativeElement; + const childGridNested = hierarchicalGrid.gridAPI.getChildGrid([targetRoot, targetNested]).nativeElement; expect(childGridNested).not.toBe(undefined); const parentBottom = childGrid.getBoundingClientRect().bottom; const parentTop = childGrid.getBoundingClientRect().top; // check it's in view within its parent expect(childGridNested.getBoundingClientRect().bottom <= parentBottom && childGridNested.getBoundingClientRect().top >= parentTop); - done(); - }); }); }); }); diff --git a/projects/igniteui-angular/grids/tree-grid/src/tree-grid-summaries.spec.ts b/projects/igniteui-angular/grids/tree-grid/src/tree-grid-summaries.spec.ts index e45e2c4c3b5..71d8f5bb4ae 100644 --- a/projects/igniteui-angular/grids/tree-grid/src/tree-grid-summaries.spec.ts +++ b/projects/igniteui-angular/grids/tree-grid/src/tree-grid-summaries.spec.ts @@ -1534,6 +1534,7 @@ describe('IgxTreeGrid - Summaries #tGrid', () => { it('Should not change active summary cell when press Arrow Down and it is last summary row', async () => { treeGrid.expandAll(); fix.detectChanges(); + await wait(16); treeGrid.verticalScrollContainer.scrollTo(treeGrid.dataView.length - 1); await wait(100);