fix(perf): Apply base grid scroll performance optimizations.#16708
fix(perf): Apply base grid scroll performance optimizations.#16708rkaraivanov merged 34 commits intomasterfrom
Conversation
|
The changes look good and functionality works as expected. I did notice one minor behavior difference while testing:
This is very minor, but wanted to flag it in case it's worth investigating. Otherwise, I'm verifying the PR. |
Performance Optimization SummaryDetailed Optimizations
Aggregate Impact Estimate (100k rows, 100 scroll operations)
|
Please ignore my previous message. Everything seems to be working as expected, likely due to temporary high resource usage. |
There was a problem hiding this comment.
Pull request overview
This PR implements performance optimizations for grid scrolling to address issue #16709. The changes focus on reducing layout reflows and unnecessary change detection cycles during scroll operations.
Changes:
- Introduced throttled scroll handling with configurable
SCROLL_THROTTLE_TIMEinjection token (default 40ms) - Replaced CSS
topproperty withtransform: translateY()to minimize layout reflows during scrolling - Moved data difference checking from
ngDoCheckto an explicitresolveDataDiff()method triggered only on data changes - Removed
@HostListenerfor scroll events that caused unnecessary layout reads
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| grid-base.directive.ts | Added throttled scroll handling with new SCROLL_THROTTLE_TIME token and scrollNotify Subject |
| for_of.directive.ts | Replaced top style with transform: translateY(), moved diff checking from ngDoCheck to resolveDataDiff(), used afterNextRender for DOM updates |
| base.helper.component.ts | Removed @HostListener for scroll events to prevent layout reflow |
| grid-navigation.service.ts | Updated containerTopOffset to read from transform instead of top |
| api.service.ts | Added array reference changes after mutations to ensure change detection |
| grid.crud.spec.ts | Disabled tests that relied on automatic array mutation detection |
| *.spec.ts (multiple) | Updated test wait times and added SCROLL_THROTTLE_TIME provider configuration for tests |
| CHANGELOG.md | Documented breaking change regarding array mutation detection |
| app.component.ts/html | Added performance testing functionality |
| styles.scss | Fixed import path for themes |
| pivot-grid.component.ts | Changed date hierarchy configuration |
| grid.component.html | Disabled filtering in performance test component |
Comments suppressed due to low confidence (3)
projects/igniteui-angular/directives/src/directives/for-of/for_of.directive.ts:1
- Missing semicolons at the end of both statements. While JavaScript allows this, it's inconsistent with the codebase style and can lead to issues.
import { NgForOfContext } from '@angular/common';
projects/igniteui-angular/directives/src/directives/for-of/for_of.directive.ts:1
- The function will return
NaNiftransformis undefined/null or doesn't match the pattern, potentially causing calculation errors downstream. Add a fallback value such asreturn Number(transform?.match(/translateY\((-?\d+\.?\d*)px\)/)?.[1]) || 0;
import { NgForOfContext } from '@angular/common';
projects/igniteui-angular/grids/grid/src/grid.master-detail.spec.ts:1
- The
detectChanges()method does not accept a parameter. This should likely befixture.detectChanges();followed byawait wait(DEBOUNCE_TIME);
import { Component, ViewChild, OnInit, DebugElement, QueryList, TemplateRef, ViewChildren } from '@angular/core';
|
|
||
| public set igxForOf(value: U & T[] | null) { | ||
| this._igxForOf = value; | ||
| if(this._differ) { |
There was a problem hiding this comment.
Missing space after if keyword. Should be if (this._differ) to match Angular style guide conventions.
| if(this._differ) { | |
| if (this._differ) { |
| }); | ||
| }); |
There was a problem hiding this comment.
Inconsistent indentation/formatting. The closing brace and parenthesis formatting doesn't follow the project's style shown elsewhere in the file.
| }); | |
| }); | |
| }); | |
| }); |
| 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)); |
There was a problem hiding this comment.
The variable name r is non-descriptive. Use a more meaningful name like resolve for the Promise resolver.
| await new Promise(r => setTimeout(r, 50)); | |
| await new Promise(resolve => setTimeout(resolve, 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); |
There was a problem hiding this comment.
Inconsistent spacing around the + operator. Should be 'Long Tasks: ' + longTask.length + ', ' for consistency.
| console.log('Long Tasks:'+ longTask.length + ", ", 'Average Long Task Time:', avgTime); | |
| console.log('Long Tasks: ' + longTask.length + ', ', 'Average Long Task Time:', avgTime); |
| const offset = this.igxForScrollOrientation === 'horizontal' ? | ||
| parseInt(this.dc.instance._viewContainer.element.nativeElement.style.left, 10) : | ||
| parseInt(this.dc.instance._viewContainer.element.nativeElement.style.top, 10); | ||
| Number(this.dc.instance._viewContainer.element.nativeElement.style.transform?.match(/translateY\((-?\d+\.?\d*)px\)/)?.[1]); |
There was a problem hiding this comment.
Same issue as containerTopOffset - will return NaN if transform is undefined or doesn't match. Should have a fallback: Number(...) || 0
Closes #16709
transforminstead oftopto limit layout reflow.scrollTopto prevent unnecessary layout reflow.afterNextRenderwhen updating position of container in DOM.