Bug
cdk-virtual-scroll-viewport inside a mat-select or mat-autocomplete should align with the overlay panel.
<mat-form-field>
<mat-select placeholder="State">
<cdk-virtual-scroll-viewport [itemSize]="10">
<mat-option *cdkVirtualFor="let state of states" [value]="state">
{{state}}
</mat-option>
</cdk-virtual-scroll-viewport>
</mat-select>
</mat-form-field>
When scrolled cdkVirtualFor does not seem to add more elements and moreover shows a big space from last element of the initial set of items to the end.
https://stackblitz.com/edit/angular-h4xptu-dgjd87?file=app/select-reset-example.html
Occasionally I run into scenarios where the items in the list are too many and makes the page unresponsive.
Seem-less integration of cdk-virtual-scroll-viewport with other components.
mat-select essentially can handle any number of elements when this works.
Angular CLI: 6.1.5
Node: 10.0.0
OS: win32 x64
Angular: 6.1.4
... animations, common, compiler, core, forms, http
... platform-browser, platform-browser-dynamic, router
Package Version
------------------------------------------------------------
@angular-devkit/architect 0.7.5
@angular-devkit/build-angular 0.7.5
@angular-devkit/build-ng-packagr 0.7.5
@angular-devkit/build-optimizer 0.7.5
@angular-devkit/build-webpack 0.7.5
@angular-devkit/core 0.7.5
@angular-devkit/schematics 0.7.5
@angular/cdk 6.4.7
@angular/cdk-experimental 6.4.7
@angular/cli 6.1.5
@angular/compiler-cli 6.1.7
@angular/flex-layout 6.0.0-beta.17
@angular/language-service 6.1.7
@angular/material 6.4.7
@ngtools/webpack 6.1.5
@schematics/angular 0.7.5
@schematics/update 0.7.5
rxjs 6.2.2
typescript 2.9.2
webpack 4.9.2
Great work
https://stackblitz.com/edit/angular-h4xptu-zyj4yh?file=styles.css
Added class virtual-scroll to mat-select
<mat-select placeholder="State" class="virtual-scroll">
and add global styles as below:-
In styles.css add
.mat-select-panel.virtual-scroll {
max-height: 100% !important;
overflow: inherit !important;
}
.mat-select-panel .cdk-virtual-scroll-viewport {
max-height: 240px !important;
}
.mat-select-panel .cdk-virtual-scroll-content-wrapper {
position: inherit !important;
top: inherit !important;
left: 0;
}
I think the real solution would be that somehow mat-select-panel ideally starts behaving like cdk-virtual-scroll-viewport which is what this workaround crudely tries to do in totality by negating scrolling properties of mat-select-panel and having cdk-virtual-scroll-viewport take charge.
Not quite the workaround above: The above workaround has problems when the overlay is closed and reopened and scrolled in between.
When scrolled to a few elements down and the select panel is reopened next time we see a blank space above.
This is solved by setRenderedRange to the initial range which gets set initially on the scrolledIndexChange event.
Then further when the mat-select emits openedChange on close we reset the rendered range to the initialRange that we preserved upfront.
https://stackblitz.com/edit/angular-h4xptu-kuu5xg?file=app%2Fselect-reset-example.ts
scrolledIndexChange($event) {
if (!this.initialRange) {
this.initialRange = this.cdkVirtualScrollViewport.getRenderedRange();
}
}
openedChange(opened) {
if (!opened) {
this.cdkVirtualScrollViewport.setRenderedContentOffset(0);
this.cdkVirtualScrollViewport.setRenderedRange(this.initialRange)
}
}
Another issue with mat-select (for anyone working on this):-
If the currently selected value is not in the mat-options the SelectionModel fails because the world according it is limited to the options query which may not have this options if it is out of range.
Workaround:
Supplement additional mat-option in addition to mat-option that has cdkVirtualScroll.
<mat-option *ngIf="selectCtrl?.value" [value]="selectCtrl?.value"></mat-option>
Related to https://github.com/angular/material2/issues/10122
@yogeshgadge Thanks for posting these findings, good stuff to know.
workarround for mat select using @yogeshgadge s approach with a form control
(css in stylus syntax)
<ng-container *ngIf="isMultiple">
<mat-option class="selected-options-bottom" *ngFor="let option of control.value" [value]="option">{{option.caption}}</mat-option>
</ng-container>
<ng-container *ngIf="!isMultiple">
<mat-option class="selected-options-bottom" *ngIf="control.value" [value]="control.value">{{control.value.caption}}</mat-option>
</ng-container>
with css:
.selected-options-bottom
visibility hidden
position absolute
giving the mat-select a panelClass, e.g. custom-mat-select with
::ng-deep .mat-select-panel.custom-mat-select
overflow hidden
max-width 280px
I just gave the example in the issue desription a quick look - itemSize should be the height of individual items, not the length of the array? Setting a height on the
Also cdk-virtual-scroll-viewport inside a mat-menu doesn't work properly. Sometimes, blank space appears with mouse wheel scrollling.
StackBlitz
@StefanoLucchi Same thing happen to me. And when you reopen the menu it's empty
@StefanoLucchi @justindiaw I had the same problem, but helped me increasing minBufferPx and maxBufferPx on cdk-virtual-scroll-viewport to eg. 300 and 600
A slight change to @yogeshgadge's solution and changing the buffers as suggested by @kubex320 solved all the white space issues.
<mat-select
(openedChange)="onOpenedChange($event)"
>
<cdk-virtual-scroll-viewport
[itemSize]="optionSizePx"
[style.height.px]="numOptionsToShow * optionSizePx"
[minBufferPx]="300"
[maxBufferPx]="600"
>
<mat-option
*cdkVirtualFor="let item of listOptions | async; let index = index"
[value]="item.value"
(onSelectionChange)="onSelectionChange(index)"
>
{{item.viewValue}}
</mat-option>
</cdk-virtual-scroll-viewport>
</mat-select>
@Input()
optionSizePx = 48;
@Input()
numOptionsToShow = 5;
@ViewChild(CdkVirtualScrollViewport, {static: true})
cdkVirtualScrollViewport: CdkVirtualScrollViewport;
selectedIndex: number;
onOpenedChange(isOpen) {
if (isOpen && this.selectedIndex) {
this.cdkVirtualScrollViewport.setRenderedContentOffset(0);
this.cdkVirtualScrollViewport.scrollToIndex(this.selectedIndex);
}
}
onSelectionChange(i: number) {
this.selectedIndex = i;
}
Not quite the workaround above: The above workaround has problems when the overlay is closed and reopened and scrolled in between.
When scrolled to a few elements down and the select panel is reopened next time we see a blank space above.
This is solved by
setRenderedRangeto the initial range which gets set initially on thescrolledIndexChangeevent.
Then further when the mat-select emitsopenedChangeon close we reset the rendered range to theinitialRangethat we preserved upfront.https://stackblitz.com/edit/angular-h4xptu-kuu5xg?file=app%2Fselect-reset-example.ts
scrolledIndexChange($event) { if (!this.initialRange) { this.initialRange = this.cdkVirtualScrollViewport.getRenderedRange(); } } openedChange(opened) { if (!opened) { this.cdkVirtualScrollViewport.setRenderedContentOffset(0); this.cdkVirtualScrollViewport.setRenderedRange(this.initialRange) } }
Exactly what I was looking for. Thank you. I was wondering why the panel seems to show white div space when scroll in between and close and reopen again.
Thanks to those who shared workarounds - it's very helpful! Anyone got the keyboard navigation (up/down arrow keys to browse through the options) working with virtual scrolling? It doesn't scroll when I use keyboard and I couldn't figure out how to get this working. I'm thinking of implementing custom keyboard event handlers for up/down arrow keys and manually setting the virtual scroll position (or the rendered index), but not sure if it's the best way to do as mat-select already has the keyboard handlers implemented. Would appreciate any inputs on this :)
I had a issue with only seeing a white dropdown after I selected a option and reopened the select. This only happened when I selected an option where I needed to scroll to get to.
I solved it purely in the template and just want to leave this here for reference in case someone else stumbles across this:
It's basically just a combination of a template variable and calling scrollToIndex on it
<mat-form-field>
<mat-select [formControl]="form" placeholder="State" class="virtual-scroll"
(openedChange)="scrollViewport.scrollToIndex(states.indexOf(form.value))">
<cdk-virtual-scroll-viewport itemSize="10" #scrollViewport>
<mat-option *cdkVirtualFor="let state of states" [value]="state">
{{state}}
</mat-option>
</cdk-virtual-scroll-viewport>
</mat-select>
</mat-form-field>
https://stackblitz.com/edit/angular-h4xptu-mmtdbj?file=app%2Fselect-reset-example.html
Most helpful comment
Also cdk-virtual-scroll-viewport inside a mat-menu doesn't work properly. Sometimes, blank space appears with mouse wheel scrollling.
StackBlitz