feat: Added virtual scroll component and sample implementation - #17281
rkaraivanov wants to merge 34 commits into
Conversation
…he view when the data source changes.
Fixed several issues with virtual coordinates mapping and the scroll engine in general. The Binary Indexed Tree (BIT) is used to efficiently calculate the cumulative heights of items in the virtual scroll, which allows for faster updates and smoother scrolling experience.
…-scroll-component
…-scroll-component
…-scroll-component
…-scroll-component
…-scroll-component
…-scroll-component
…-scroll-component
…-scroll-component
…-scroll-component
There was a problem hiding this comment.
Pull request overview
This PR introduces a new IgxVirtualScrollComponent entry point to Ignite UI for Angular, providing a self-contained virtualized viewport for vertical and horizontal scrolling (including variable-size items and remote/infinite loading), and adds a demo route + sample showcasing usage.
Changes:
- Added a new
igniteui-angular/virtual-scrollentry point with a virtual scroll component, item-template directive, supporting types, and a scroll-math engine. - Added unit tests covering the engine behavior, template context, orientations (including RTL), outputs, and basic rendering.
- Added a demo/sample implementation and wired it into the demo app routing + navigation list.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/app/virtual-scroll/virtual-scroll.sample.ts | Demo component showcasing vertical/horizontal + remote data scenarios. |
| src/app/virtual-scroll/virtual-scroll.sample.html | Sample template demonstrating igxVirtualItem usage for multiple scenarios. |
| src/app/virtual-scroll/virtual-scroll.sample.scss | Styling for the new demo page. |
| src/app/app.routes.ts | Adds a /virtual-scroll route for the demo. |
| src/app/app.component.ts | Adds “Virtual Scroll” to the demo navigation list. |
| projects/igniteui-angular/virtual-scroll/src/virtual-scroll/virtual-scroll.component.ts | Core virtual scroll component implementation (signals-first + view pooling). |
| projects/igniteui-angular/virtual-scroll/src/virtual-scroll/virtual-scroll.component.html | Component template for track/content positioning. |
| projects/igniteui-angular/virtual-scroll/src/virtual-scroll/virtual-scroll.component.scss | Component styles for vertical/horizontal layouts. |
| projects/igniteui-angular/virtual-scroll/src/virtual-scroll/virtual-scroll-item.directive.ts | Directive to capture ng-template[igxVirtualItem] as the item template. |
| projects/igniteui-angular/virtual-scroll/src/virtual-scroll/types.ts | Typed template context + output payload types. |
| projects/igniteui-angular/virtual-scroll/src/virtual-scroll/scroll-engine.ts | Scroll math engine (BIT-based sizing, visible range computation, coordinate compression). |
| projects/igniteui-angular/virtual-scroll/src/virtual-scroll/virtual-scroll.component.spec.ts | Unit tests for engine + component behaviors (including RTL). |
| projects/igniteui-angular/virtual-scroll/src/public_api.ts | Public exports for the new entry point. |
| projects/igniteui-angular/virtual-scroll/index.ts | Entry-point barrel export. |
| projects/igniteui-angular/virtual-scroll/ng-package.json | ng-packagr entry point config placeholder (consistent with other entry points). |
| projects/igniteui-angular/virtual-scroll/README.md | Feature documentation and usage examples for the new component. |
| projects/igniteui-angular/src/public_api.ts | Exposes igniteui-angular/virtual-scroll from the root package exports. |
Comments suppressed due to low confidence (1)
src/app/virtual-scroll/virtual-scroll.sample.html:107
- Section numbering in the sample comments labels this as section 6 even though it is the 5th sample block. Renumber to keep the sample easier to follow.
<!-- 6. Remote / infinite scrolling -->
<!-- ------------------------------------------------------------------ -->
….com/IgniteUI/igniteui-angular into rkaraivanov/virtual-scroll-component
…-scroll-component
|
@simeonoff |
Restructured the CSS and sample. Should be more in line with the other component styles now and the approach we wanna use in the future for authoring component styles. |
…#17579) * feat(virtual-scroll): add initial viewport hint A list inside a popup is hidden until the change detection pass that reveals it, so it has no size to measure and renders nothing in that pass. initialViewportSize gives that first render a size to work from; the measured size takes over as soon as the host can be measured. * fix(virtual-scroll): measure a laid-out host at its real size * feat(virtual-scroll): support windowed data * refactor(excel-filter): use virtual scroll The search list is virtualized by IgxVirtualScrollComponent instead of the igxFor directive. Rows are measured in the DOM rather than assumed from a configured item and container size, and initialViewportSize gives the list the height the menu allots it so the first window renders in the pass that opens it. aria-activedescendant now names only a rendered element: navigateItem clears it while the target is off screen and restores it once the scroll has rendered that row, and a scroll that recycles the wrappers no longer leaves it pointing at an element that is gone. * fix(excel-filter): clear stale active descendant * fix(virtual-scroll): report a moved range with an unchanged loaded part * fix(excel-filter): name the focused row after clearing a search * feat(combo): use IgxVirtualScrollComponent for the item list Migrates IgxComboComponent and IgxSimpleComboComponent from the igxFor directive to IgxVirtualScrollComponent, following the Excel style filtering search list. IgxDropDownComponent now also accepts a content-projected igx-virtual-scroll; *igxFor keeps working as documented, and selection and navigation behave the same either way. A row is measured in the DOM once it renders and the measured size replaces the estimate it started from, so the scrollable extent follows the real height of the rows that have rendered. * fix(drop-down): reconcile virtualized item queries * docs(*): tighten the comments added by the virtual scroll migration * fix(*): correct remote paging, virtualized navigation and ARIA * fix(drop-down): select a loaded index that has no rendered row * docs(virtual-scroll): state the per-axis viewport hint contract * perf(combo): resolve the selection without scanning per key * test(excel-filter): wait for the virtualized list to render * fix(*): follow the virtual scroll's renamed row markup * fix(combo): keep an arriving page at the index it was requested from * test(esf): assert the search list against the pass that delivers its values * refactor(virtual-scroll): reshape the paging API before release A page whose `totalCount` differs from the previous one comes from another collection, a filtered one for instance, so no measured size is retained across it. Same-count pages keep sizes per index as before. `initialViewportSize` is marked `@hidden @internal`. It exists because the drop-down family reads and focuses items in the pass that opens the list, which no measurement can serve in time. Hiding it keeps its later removal from being a breaking change once those components render on reveal. --------- Co-authored-by: Radoslav Karaivanov <rkaraivanov@infragistics.com>
…-scroll-component
| this.children.changes | ||
| .pipe(takeUntil(this.destroy$)) | ||
| .subscribe(() => afterNextRender( | ||
| () => this.refreshActiveDescendant(), |
There was a problem hiding this comment.
Can we do the same ESF does - it only marksForCheck if the id actually changed.
| this.virtDir.igxForItemSize = this.itemSize; | ||
| this.virtDir.recalcUpdateSizes(); | ||
| // Only flushes the bindings the surrounding menu changed; the list measures itself. | ||
| if (this.virtualScroll && !(this.cdr as any).destroyed) { |
There was a problem hiding this comment.
can you use the component's own destroy flag?
| ### General | ||
|
|
||
| - The Excel style filtering search list, `IgxComboComponent` and `IgxSimpleComboComponent` are now virtualized by `IgxVirtualScrollComponent` instead of the `igxFor` directive. A row is measured in the DOM once it renders and the measured size replaces the estimate it started from; rows that have not rendered keep that estimate. | ||
| - The list markup changed accordingly: `igx-display-container` and the `igx-vhelper--vertical` scrollbar are replaced by the `igx-virtual-scroll` host and its `igx-vs__item` row wrappers. Applications and tests that reach into those elements directly need updating. |
There was a problem hiding this comment.
What are these wrappers? Shouldn't this be just igx-virtual-item as children of igx-virtual-scroll
| @ViewChild(IgxForOfDirective, { read: IgxForOfDirective, static: true }) | ||
| protected virtDir!: IgxForOfDirective<any>; | ||
| @ViewChild('virtualScroll', { static: true }) | ||
| public virtualScrollContainer!: IgxVirtualScrollComponent<any>; |
There was a problem hiding this comment.
the type change is a breaking one and should be documented as such and also requires a migration. Same for ESF
| * A virtual scroll component for large lists. Only the items visible in the | ||
| * viewport (plus a configurable over-scan) are rendered. | ||
| * | ||
| * @igxModule IgxVirtualScrollModule |
There was a problem hiding this comment.
Don't think we have this module right? This component is strictly stand-alone
… change Two regressions in IgxVirtualScrollComponent, captured as tests that fail on the current implementation: - scrollToIndex with block/inline 'nearest' start-aligns an item that is out of view. Native scrollIntoView semantics end-align an item below or past the viewport, which is what drop-down, combo and the Excel-style filter rely on for ArrowDown navigation. - _contentTransform reads the host direction through _isLTR() inside a computed(), so a runtime dir change on a horizontal list does not flip the translateX sign while the rendered range stays the same. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Some findings during agentic testing:
Both have tests added that will fail in the latest commit. We need to fix these before merge. |
Three more regressions captured as tests that fail on the current code: - Under coordinate compression the visible range at the maximum DOM scroll offset stops short of the last items, so the tail of a very long list can never be reached. - Replacing `data` with rows of unchanged size discards their measurements but never re-measures the recycled wrappers, leaving the engine on the estimate. - navigateFirst on a remote page that starts past index 0 lands on the group header once the first page arrives. The spec now asserts the loaded page starts past 0, so it cannot pass through the local path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Added 3 more tests that fail due to actual issues:
|
Closes #17607
Closes #17608
Closes #17610
Closes #17611
Closes #17615
Closes #17616
Description
Expose a new virtual scroll component for horizontal/vertical scrolling.
Motivation / Context
igxForOfrequires the developer to manually wire a separate scroll container, aVirtualHelperComponent/HVirtualHelperComponentDOM helper element, and passigxForContainerSizeexplicitly. That's 3–4 pieces of setup vs. one tag.igxForOfusesViewContainerRef.move()but always keeps a fixed chunk alive, paying create/destroy cost when chunk size changes.ChangeDetectionStrategy.OnPushthroughout the implementation.Type of Change (check all that apply):
Component(s) / Area(s) Affected:
How Has This Been Tested?
Test Configuration:
Screenshots / Recordings
Checklist:
feature/README.MDupdates for the feature docsREADME.MDCHANGELOG.MDupdates for newly added functionalityng updatemigrations for the breaking changes (migrations guidelines)