Skip to content

fix(perf): Apply base grid scroll performance optimizations.#16708

Merged
rkaraivanov merged 34 commits intomasterfrom
mkirova/performance-optimizations
Jan 23, 2026
Merged

fix(perf): Apply base grid scroll performance optimizations.#16708
rkaraivanov merged 34 commits intomasterfrom
mkirova/performance-optimizations

Conversation

@MayaKirova
Copy link
Contributor

@MayaKirova MayaKirova commented Jan 8, 2026

Closes #16709

  • throttle scroll
  • move diff check out of ngDoCheck
  • use transform instead of top to limit layout reflow.
  • remove HostListener that reads scrollTop to prevent unnecessary layout reflow.
  • remove zone runs where possible.
  • use afterNextRender when updating position of container in DOM.

@MayaKirova MayaKirova changed the title fix(perf): Apply some performance optimizations. fix(perf): Apply base grid scroll performance optimizations. Jan 13, 2026
@MayaKirova MayaKirova marked this pull request as ready for review January 21, 2026 15:10
@dkamburov dkamburov added 💥 status: in-test PRs currently being tested ✅ status: verified Applies to PRs that have passed manual verification and removed ❌ status: awaiting-test PRs awaiting manual verification 💥 status: in-test PRs currently being tested labels Jan 23, 2026
@dkamburov
Copy link
Contributor

dkamburov commented Jan 23, 2026

The changes look good and functionality works as expected.

I did notice one minor behavior difference while testing:

  • Issue: When holding Tab to navigate between cells during editing, the navigation feels slightly slower than before
  • Possible cause: This might be related to the throttle timing or the transform scroll implementation

This is very minor, but wanted to flag it in case it's worth investigating. Otherwise, I'm verifying the PR.

@dkamburov
Copy link
Contributor

Performance Optimization Summary

Detailed Optimizations

Optimization Technique Before (Baseline) After (Optimized) Expected Delta
Array allocation push() → pre-allocated new Array(n) O(n) resizes with copying O(1) direct assignment ~20-40% faster for sizesCache building
DOM element lookup Repeated .map() → cached getter Re-computed every scroll Single traversal, cached ~15-25% fewer CPU cycles per scroll
Change detection ngDoCheck → property setter Called every CD cycle Called only on data change ~30-50% fewer CD calls
CSS positioning style.top → transform: translateY() Triggers layout reflow GPU-accelerated, no reflow ~50-70% faster paint
Scroll event handling Raw events → 40ms throttle ~60+ events/sec processed ~25 events/sec processed ~60% fewer scroll handlers
Scroll helper listener @HostListener removed Duplicate event processing Single event path ~10-15% less overhead

Aggregate Impact Estimate (100k rows, 100 scroll operations)

Metric Before (est.) After (est.) Delta
Long Tasks Count ~80-120 ~30-50 -50% to -60%
Avg Long Task Duration ~80-120ms ~55-75ms -30% to -40%
Total Blocking Time ~8-12 sec ~3-5 sec -50% to -60%
Frames per second ~15-25 fps ~40-55 fps +100% to +150%

@rkaraivanov rkaraivanov merged commit bae85ea into master Jan 23, 2026
6 checks passed
@rkaraivanov rkaraivanov deleted the mkirova/performance-optimizations branch January 23, 2026 16:09
@dkamburov
Copy link
Contributor

The changes look good and functionality works as expected.

I did notice one minor behavior difference while testing:

  • Issue: When holding Tab to navigate between cells during editing, the navigation feels slightly slower than before
  • Possible cause: This might be related to the throttle timing or the transform scroll implementation

This is very minor, but wanted to flag it in case it's worth investigating. Otherwise, I'm verifying the PR.

Please ignore my previous message. Everything seems to be working as expected, likely due to temporary high resource usage.

@kdinev kdinev requested a review from Copilot January 30, 2026 14:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_TIME injection token (default 40ms)
  • Replaced CSS top property with transform: translateY() to minimize layout reflows during scrolling
  • Moved data difference checking from ngDoCheck to an explicit resolveDataDiff() method triggered only on data changes
  • Removed @HostListener for 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 NaN if transform is undefined/null or doesn't match the pattern, potentially causing calculation errors downstream. Add a fallback value such as return 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 be fixture.detectChanges(); followed by await 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) {
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after if keyword. Should be if (this._differ) to match Angular style guide conventions.

Suggested change
if(this._differ) {
if (this._differ) {

Copilot uses AI. Check for mistakes.
Comment on lines +638 to +639
});
});
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation/formatting. The closing brace and parenthesis formatting doesn't follow the project's style shown elsewhere in the file.

Suggested change
});
});
});
});

Copilot uses AI. Check for mistakes.
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));
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name r is non-descriptive. Use a more meaningful name like resolve for the Promise resolver.

Suggested change
await new Promise(r => setTimeout(r, 50));
await new Promise(resolve => setTimeout(resolve, 50));

Copilot uses AI. Check for mistakes.
}
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);
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent spacing around the + operator. Should be 'Long Tasks: ' + longTask.length + ', ' for consistency.

Suggested change
console.log('Long Tasks:'+ longTask.length + ", ", 'Average Long Task Time:', avgTime);
console.log('Long Tasks: ' + longTask.length + ', ', 'Average Long Task Time:', avgTime);

Copilot uses AI. Check for mistakes.
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]);
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as containerTopOffset - will return NaN if transform is undefined or doesn't match. Should have a fallback: Number(...) || 0

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

grid: performance version: 21.1.x ✅ status: verified Applies to PRs that have passed manual verification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Virtualization performance improvements

3 participants