Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

virtual-scroll inside mat-select new values not visible & big space #13087

Open
yogeshgadge opened this issue Sep 11, 2018 · 17 comments
Open

virtual-scroll inside mat-select new values not visible & big space #13087

yogeshgadge opened this issue Sep 11, 2018 · 17 comments
Labels
area: cdk/scrolling area: material/form-field area: material/select P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@yogeshgadge
Copy link

yogeshgadge commented Sep 11, 2018

Bug, feature request, or proposal:

Bug

What is the expected behavior?

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>

What is the current behavior?

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.

What are the steps to reproduce?

https://stackblitz.com/edit/angular-h4xptu-dgjd87?file=app/select-reset-example.html

What is the use-case or motivation for changing an existing behavior?

  • 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.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

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

Is there anything else we should know?

Great work

@yogeshgadge
Copy link
Author

yogeshgadge commented Sep 12, 2018

Workaround:-

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.

@yogeshgadge
Copy link
Author

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)
    } 
  }

@yogeshgadge
Copy link
Author

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>

@mmalerba
Copy link
Contributor

Related to #10122

@yogeshgadge Thanks for posting these findings, good stuff to know.

@danderwald
Copy link

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

@yusijs
Copy link

yusijs commented Nov 30, 2018

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 element + [itemSize]=48 works (albeit, it looks a bit crap when there are less than 4 options).

@StefanoLucchi
Copy link

Also cdk-virtual-scroll-viewport inside a mat-menu doesn't work properly. Sometimes, blank space appears with mouse wheel scrollling.
StackBlitz

@justindiaw
Copy link

@StefanoLucchi Same thing happen to me. And when you reopen the menu it's empty

@kubex320
Copy link

kubex320 commented Dec 19, 2018

@StefanoLucchi @justindiaw I had the same problem, but helped me increasing minBufferPx and maxBufferPx on cdk-virtual-scroll-viewport to eg. 300 and 600

@mmalerba mmalerba added needs triage This issue needs to be triaged by the team area: cdk/scrolling area: material/form-field area: material/select P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent and removed needs triage This issue needs to be triaged by the team labels May 20, 2020
@jsdevtom
Copy link

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;
  }

@gitalvininfo
Copy link

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)
    } 
  }

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.

@tsriram
Copy link

tsriram commented Jan 8, 2021

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 :)

@kaihenzler
Copy link

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

@stpwebdev
Copy link

Another smaller issue I saw here was that mat-active is being inappropriately assigned to options in the virtual scrolling component.
Using yogeshgadge's stackblitz (https://stackblitz.com/edit/angular-h4xptu-zyj4yh?file=styles.css) you can see that if you select the first option and then reopen the dropdown and start scrolling down, every 11th or so option will have the mat-active class set on it.

@dickinr
Copy link

dickinr commented Oct 19, 2021

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 :)

Did you ever figure out a way to do this? I'm currently trying to implement this, but it seems like set scrollToIndex or setRenderedRange stops the next option from being selected/highlighted/focused.

@joshuawwright
Copy link

@dickinr I found that all of the solutions above result in a hacky solution. I looked at the source code, but couldn't find a reasonable way of getting the virtual scrolling to work, without rewriting the component. I think it would be best to either create a custom component, or to use a different UI component. Maybe a filtered autocomplete would fit your purposes: https://material.angular.io/components/autocomplete/examples

@JoranLive
Copy link

Hello,

We are using Angular 16 and still facing issue.

If we select an item wich is not in default rendered view range (0 to N), the viewport appears blank until a scroll event.

Is there some news ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: cdk/scrolling area: material/form-field area: material/select P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
None yet
Development

No branches or pull requests