angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.39k stars 6.76k forks source link

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

Open yogeshgadge opened 6 years ago

yogeshgadge commented 6 years ago

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?

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

Related to https://github.com/angular/material2/issues/10122

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

danderwald commented 6 years ago

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 commented 6 years ago

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 commented 5 years ago

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

justindiaw commented 5 years ago

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

kubex320 commented 5 years ago

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

jsdevtom commented 4 years ago

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 commented 4 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

@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 commented 1 year ago

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 ?