From 66afb8a18276651a6c9533552aa1be7af81e2b1e Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 8 Jan 2026 16:55:42 +0200 Subject: [PATCH 1/7] fix(perf): Apply some performance optimizations. --- .../src/app/grid/grid.component.html | 1 + .../src/styles.scss | 2 +- .../for-of/base.helper.component.ts | 5 --- .../src/directives/for-of/for_of.directive.ts | 33 ++++++++++------- .../grids/grid/src/grid-base.directive.ts | 36 +++++++++++++------ 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/projects/igniteui-angular-performance/src/app/grid/grid.component.html b/projects/igniteui-angular-performance/src/app/grid/grid.component.html index 9cad429ffa8..309d817ea83 100644 --- a/projects/igniteui-angular-performance/src/app/grid/grid.component.html +++ b/projects/igniteui-angular-performance/src/app/grid/grid.component.html @@ -1,5 +1,6 @@
{ ], standalone: true }) -export class IgxForOfDirective extends IgxForOfToken implements OnInit, OnChanges, DoCheck, OnDestroy, AfterViewInit { +export class IgxForOfDirective extends IgxForOfToken implements OnInit, OnChanges, OnDestroy, AfterViewInit { private _viewContainer = inject(ViewContainerRef); protected _template = inject>>(TemplateRef); protected _differs = inject(IterableDiffers); @@ -93,7 +93,7 @@ export class IgxForOfDirective extends IgxForOfToken extends IgxForOfToken extends IgxForOfToken extends IgxForOfToken extends IgxForOfToken extends IgxForOfContext selector: '[igxGridFor][igxGridForOf]', standalone: true }) -export class IgxGridForOfDirective extends IgxForOfDirective implements OnInit, OnChanges, DoCheck { +export class IgxGridForOfDirective extends IgxForOfDirective implements OnInit, OnChanges { protected syncService = inject(IgxForOfSyncService); @Input() @@ -1626,7 +1633,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec this.syncService.setMaster(this, true); } - public override ngDoCheck() { + public override resolveDataDiff() { if (this._differ) { const changes = this._differ.diff(this.igxForOf); if (changes) { @@ -1660,19 +1667,21 @@ export class IgxGridForOfDirective extends IgxForOfDirec } public override onScroll(event) { + this.scrollComponent.scrollAmount = event.target.scrollTop; if (!parseInt(this.scrollComponent.nativeElement.style.height, 10)) { return; } if (!this._bScrollInternal) { - this._calcVirtualScrollPosition(event.target.scrollTop); + this._calcVirtualScrollPosition(this.scrollComponent.scrollAmount); } else { this._bScrollInternal = false; } const scrollOffset = this.fixedUpdateAllElements(this._virtScrollPosition); - this.dc.instance._viewContainer.element.nativeElement.style.top = -(scrollOffset) + 'px'; + requestAnimationFrame(() => { + this.dc.instance._viewContainer.element.nativeElement.style.transform = `translateY(${-scrollOffset}px)`; + }); - this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); this.cdr.markForCheck(); } diff --git a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts index cb01b0beff7..feaf3c1cc89 100644 --- a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts +++ b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts @@ -90,7 +90,8 @@ import { IGridResourceStrings, IgxOverlayOutletDirective, DEFAULT_LOCALE, - onResourceChangeHandle + onResourceChangeHandle, + PerformanceService } from 'igniteui-angular/core'; import { IgcTrialWatermark } from 'igniteui-trial-watermark'; import { Subject, pipe, fromEvent, animationFrameScheduler, merge } from 'rxjs'; @@ -134,6 +135,7 @@ const MIN_ROW_EDITING_COUNT_THRESHOLD = 2; @Directive() export abstract class IgxGridBaseDirective implements GridType, OnInit, DoCheck, OnDestroy, AfterContentInit, AfterViewInit { + private performance = inject(PerformanceService); public readonly validation = inject(IgxGridValidationService); /** @hidden @internal */ @@ -2998,6 +3000,9 @@ export abstract class IgxGridBaseDirective implements GridType, /** @hidden @internal */ public resizeNotify = new Subject(); + /** @hidden @internal */ + public scrollNotify = new Subject(); + /** @hidden @internal */ public rowAddedNotifier = new Subject(); @@ -3709,6 +3714,15 @@ export abstract class IgxGridBaseDirective implements GridType, this.subscribeToTransactions(); + this.scrollNotify.pipe( + filter(() => !this._init), + throttleTime(40, animationFrameScheduler, { leading: false, trailing: true }), + destructor + ) + .subscribe((event) => { + this.verticalScrollHandler(event); + }); + this.resizeNotify.pipe( filter(() => !this._init), throttleTime(40, animationFrameScheduler, { leading: true, trailing: true }), @@ -3852,6 +3866,8 @@ export abstract class IgxGridBaseDirective implements GridType, public ngOnInit() { this._setupServices(); this._setupListeners(); + this.performance.setLogEnabled(true); + this.performance.attachObserver(); this.rowListDiffer = this.differs.find([]).create(null); // compare based on field, not on object ref. this.columnListDiffer = this.differs.find([]).create((_index, col: ColumnType) => col.field); @@ -4153,7 +4169,7 @@ export abstract class IgxGridBaseDirective implements GridType, this.zone.runOutsideAngular(() => { this.verticalScrollHandler = this.verticalScrollHandler.bind(this); this.horizontalScrollHandler = this.horizontalScrollHandler.bind(this); - this.verticalScrollContainer.getScroll().addEventListener('scroll', this.verticalScrollHandler); + this.verticalScrollContainer.getScroll().addEventListener('scroll', (event) => this.scrollNotify.next(event)); this.headerContainer?.getScroll().addEventListener('scroll', this.horizontalScrollHandler); if (this.hasColumnsToAutosize) { this.headerContainer?.dataChanged.pipe(takeUntil(this.destroy$)).subscribe(() => { @@ -7721,14 +7737,14 @@ export abstract class IgxGridBaseDirective implements GridType, this.verticalScrollContainer.onScroll(event); this.disableTransitions = true; - this.zone.run(() => { - this.zone.onStable.pipe(first()).subscribe(() => { - this.verticalScrollContainer.chunkLoad.emit(this.verticalScrollContainer.state); - if (this.rowEditable) { - this.changeRowEditingOverlayStateOnScroll(this.crudService.rowInEditMode); - } - }); - }); + // this.zone.run(() => { + // this.zone.onStable.pipe(first()).subscribe(() => { + // this.verticalScrollContainer.chunkLoad.emit(this.verticalScrollContainer.state); + // if (this.rowEditable) { + // this.changeRowEditingOverlayStateOnScroll(this.crudService.rowInEditMode); + // } + // }); + // }); this.disableTransitions = false; this.hideOverlays(); From 3272733157b040ec823ca96660f29a32a3b17459 Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 9 Jan 2026 16:31:14 +0200 Subject: [PATCH 2/7] chore(*): MInor tweaks to scroll handler. --- .../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 c8355a5a833..ba8f578f258 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 @@ -1668,7 +1668,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec public override onScroll(event) { this.scrollComponent.scrollAmount = event.target.scrollTop; - if (!parseInt(this.scrollComponent.nativeElement.style.height, 10)) { + if (!this.scrollComponent.size) { return; } if (!this._bScrollInternal) { @@ -1678,7 +1678,7 @@ export class IgxGridForOfDirective extends IgxForOfDirec } const scrollOffset = this.fixedUpdateAllElements(this._virtScrollPosition); - requestAnimationFrame(() => { + this._zone.onStable.pipe(first()).subscribe(() => { this.dc.instance._viewContainer.element.nativeElement.style.transform = `translateY(${-scrollOffset}px)`; }); From 62cd4e03d60e4dedf09c00b37ae5baa024ea3c0e Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 9 Jan 2026 16:35:05 +0200 Subject: [PATCH 3/7] chore(*): Minor tweak for verticalScrollHandler. --- .../grids/grid/src/grid-base.directive.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts index feaf3c1cc89..7e592fa3fce 100644 --- a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts +++ b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts @@ -7737,14 +7737,12 @@ export abstract class IgxGridBaseDirective implements GridType, this.verticalScrollContainer.onScroll(event); this.disableTransitions = true; - // this.zone.run(() => { - // this.zone.onStable.pipe(first()).subscribe(() => { - // this.verticalScrollContainer.chunkLoad.emit(this.verticalScrollContainer.state); - // if (this.rowEditable) { - // this.changeRowEditingOverlayStateOnScroll(this.crudService.rowInEditMode); - // } - // }); - // }); + this.zone.onStable.pipe(first()).subscribe(() => { + this.verticalScrollContainer.chunkLoad.emit(this.verticalScrollContainer.state); + if (this.rowEditable) { + this.changeRowEditingOverlayStateOnScroll(this.crudService.rowInEditMode); + } + }); this.disableTransitions = false; this.hideOverlays(); From c4119a0b9ebbd56bc1f270929c1766bedf94a74f Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 12 Jan 2026 13:57:01 +0200 Subject: [PATCH 4/7] chore(*): Add style writes in afterNextRender's write callback. --- .../src/app/grid/grid.component.html | 2 +- .../src/directives/for-of/for_of.directive.ts | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/projects/igniteui-angular-performance/src/app/grid/grid.component.html b/projects/igniteui-angular-performance/src/app/grid/grid.component.html index 309d817ea83..377941618c5 100644 --- a/projects/igniteui-angular-performance/src/app/grid/grid.component.html +++ b/projects/igniteui-angular-performance/src/app/grid/grid.component.html @@ -3,7 +3,7 @@ style="--ig-size: var(--ig-size-small);" #grid [data]="data" - [allowFiltering]="true" + [allowFiltering]="false" [height]="'100%'" [width]="'100%'" > 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 ba8f578f258..77419755d3b 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 } from '@angular/core'; +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 { DisplayContainerComponent } from './display.container'; import { HVirtualHelperComponent } from './horizontal.virtual.helper.component'; @@ -88,6 +88,7 @@ export class IgxForOfDirective extends IgxForOfToken>>(TemplateRef); protected _differs = inject(IterableDiffers); + protected _injector = inject(EnvironmentInjector); public cdr = inject(ChangeDetectorRef); protected _zone = inject(NgZone); protected syncScrollService = inject(IgxForOfScrollSyncService); @@ -606,7 +607,13 @@ export class IgxForOfDirective extends IgxForOfToken { + afterNextRender({ + write: () => { + this.dc.instance._viewContainer.element.nativeElement.style.transform = `translateY(${-scrollOffset}px)`; + } + }); + }); } const maxRealScrollTop = this.scrollComponent.nativeElement.scrollHeight - containerSize; @@ -901,7 +908,13 @@ export class IgxForOfDirective extends IgxForOfToken { + afterNextRender({ + write: () => { + this.dc.instance._viewContainer.element.nativeElement.style.transform = `translateY(${-scrollOffset}px)`; + } + }); + }); this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); @@ -1677,10 +1690,13 @@ export class IgxGridForOfDirective extends IgxForOfDirec this._bScrollInternal = false; } const scrollOffset = this.fixedUpdateAllElements(this._virtScrollPosition); - - this._zone.onStable.pipe(first()).subscribe(() => { - this.dc.instance._viewContainer.element.nativeElement.style.transform = `translateY(${-scrollOffset}px)`; - }); + runInInjectionContext(this._injector, () => { + afterNextRender({ + write: () => { + this.dc.instance._viewContainer.element.nativeElement.style.transform = `translateY(${-scrollOffset}px)`; + } + }); + }); this.cdr.markForCheck(); } From 751d526237e02b887fae639fd58ba8c9f6699479 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 12 Jan 2026 14:47:34 +0200 Subject: [PATCH 5/7] chore(*): Clean performance observer and custom styles. --- .../src/app/grid/grid.component.html | 2 +- .../igniteui-angular/grids/grid/src/grid-base.directive.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/projects/igniteui-angular-performance/src/app/grid/grid.component.html b/projects/igniteui-angular-performance/src/app/grid/grid.component.html index 377941618c5..dbc647f41e7 100644 --- a/projects/igniteui-angular-performance/src/app/grid/grid.component.html +++ b/projects/igniteui-angular-performance/src/app/grid/grid.component.html @@ -1,6 +1,6 @@
col.field); From eb38e63bae6c94d644f430d5bb2465480b21f475 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 12 Jan 2026 16:10:52 +0200 Subject: [PATCH 6/7] chore(*): Add btn to scroll 100 times in the grids and measure long tasks. --- .../src/app/app.component.html | 4 ++++ .../src/app/app.component.ts | 22 ++++++++++++++++++- .../app/pivot-grid/pivot-grid.component.ts | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular-performance/src/app/app.component.html b/projects/igniteui-angular-performance/src/app/app.component.html index 8ca8b694e34..277913f006f 100644 --- a/projects/igniteui-angular-performance/src/app/app.component.html +++ b/projects/igniteui-angular-performance/src/app/app.component.html @@ -5,6 +5,10 @@ {{ route.title }} } + +
diff --git a/projects/igniteui-angular-performance/src/app/app.component.ts b/projects/igniteui-angular-performance/src/app/app.component.ts index 109d49db6d3..d25206dadf1 100644 --- a/projects/igniteui-angular-performance/src/app/app.component.ts +++ b/projects/igniteui-angular-performance/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { RouterLink, RouterOutlet, Routes } from '@angular/router'; import { IgxButtonDirective } from 'igniteui-angular'; import { routes } from './app.routes'; @@ -11,4 +11,24 @@ import { routes } from './app.routes'; }) export class AppComponent { protected routes: Routes = routes; + + @ViewChild(RouterOutlet) outlet!: RouterOutlet; + + public async OnPerfTest() { + const longTask = []; + const observer = new PerformanceObserver((list) => { + longTask.push(...list.getEntries()); + }); + observer.observe({ entryTypes: ['longtask'] }); + const grid = (this.outlet.component as any).grid || (this.outlet.component as any).pivotGrid; + for (let i = 0; i < 100; i++) { + grid.navigateTo(i * 50); + await new Promise(r => setTimeout(r, 50)); + } + const sum = longTask.reduce((acc, task) => acc + task.duration, 0); + const avgTime = sum / longTask.length; + console.log('Long Tasks:'+ longTask.length + ", ", 'Average Long Task Time:', avgTime); + observer.disconnect(); + + } } diff --git a/projects/igniteui-angular-performance/src/app/pivot-grid/pivot-grid.component.ts b/projects/igniteui-angular-performance/src/app/pivot-grid/pivot-grid.component.ts index e6d9f8593af..9070c7202a8 100644 --- a/projects/igniteui-angular-performance/src/app/pivot-grid/pivot-grid.component.ts +++ b/projects/igniteui-angular-performance/src/app/pivot-grid/pivot-grid.component.ts @@ -182,9 +182,9 @@ export class PivotGridComponent { sortDirection: SortingDirection.None }, { - fullDate: false, + fullDate: true, quarters: true, - months: false, + months: true, }), ], values: [ From afaa5d78e3e3c6e9bdbfdd4efdfa83c6d3a04639 Mon Sep 17 00:00:00 2001 From: MKirova Date: Tue, 13 Jan 2026 17:20:01 +0200 Subject: [PATCH 7/7] chore(*): Update a few places where top offset is still used. --- .../src/directives/for-of/for_of.directive.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 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 77419755d3b..56c6f77d9f8 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 @@ -433,7 +433,7 @@ export class IgxForOfDirective extends IgxForOfToken extends IgxForOfToken extends IgxForOfToken