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

Adding items to an existing list will cause MatSelect to scroll to first item. #200

Closed
ArsalanSavand opened this issue Dec 13, 2019 · 13 comments · Fixed by #236
Closed

Adding items to an existing list will cause MatSelect to scroll to first item. #200

ArsalanSavand opened this issue Dec 13, 2019 · 13 comments · Fixed by #236
Labels
bug Something isn't working

Comments

@ArsalanSavand
Copy link

I'm having a list of items and searching with server client.
I'm using a library to load more items once user reaches bottom of the scroll container. Problem is when I push new items to my list, it scrolls to first item.

The problem is not with the library I'm using, because even if I just randomly push an item to my list, it happens.

@ArsalanSavand ArsalanSavand added the bug Something isn't working label Dec 13, 2019
@macjohnny
Copy link
Member

Can you please provide a stackblitz demo?

@nprasovict
Copy link

I'm using ng-mat-select-infinite-scroll component and have noticed the same behavior.

Here is working stackblitz

If you remove ngx-mat-select-search from app.component.html and reload browser view on the right, options list will not jump to start after you scroll to the end and infinite scroller adds new items to it.

If you leave ngx-mat-select-search as is in the app.component.html, every time you scroll to the end of options and infinite scroller loads a new batch of items, the list jumps to start.

@macjohnny
Copy link
Member

thanks for the example.
I think the problem is here

this._options.changes
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
const keyManager = this.matSelect._keyManager;
if (keyManager && this.matSelect.panelOpen) {
// avoid "expression has been changed" error
setTimeout(() => {
// set first item active and input width
keyManager.setFirstItemActive();

this is needed to scroll to the first option of the filtered result. Somehow we should distinguish why the options changed, due to search or due to additional loading.

@macjohnny
Copy link
Member

a first fix for this could be to add an input to disable this behavior. @nprasovict @ArsalanSavand would you like to investigate this and find a fix?

@nprasovict
Copy link

I won't have time to work on this since my team decided to go with different UX design where we don't use drop-down lists and hence won't be using ngx-mat-select-search and ng-mat-select-infinite-scroll.

@macjohnny
Copy link
Member

this issue is also reproducible in the demo by @L-NiNo: https://stackblitz.com/edit/mat-select-search-with-infinity-scroll

@macjohnny
Copy link
Member

@jvinhit would you like to give it a try and find a fix?

@vladimir-poma
Copy link

This is the first time that I do a workaround like this, but I could be able to solve this by overwriting for a moment the setFirstItemActive function when you update your list, to avoid autoscrolling by doing

<mat-form-field>
          <mat-select [(value)]="itemSelected">
            <mat-option>
              <ngx-mat-select-search
                #singleSelect
                [formControl]="filterCtrl"
                placeholderLabel="select an item">
                <mat-icon ngxMatSelectSearchClear>x</mat-icon>
              </ngx-mat-select-search>
            </mat-option>
            <mat-option
              *ngFor="let option of options"
              [value]="option._id">
              {{option.name}}
            </mat-option>
          </mat-select>
</mat-form-field>
import { MatSelect } from '@angular/material/select';
// other imports

@Component({
})
export class YourComponent implements OnInit, OnDestroy {
  options: [];
  @ViewChild('singleSelect', {static: false}) singleSelect: MatSelect;
  constructor(/**/) {
    // ...
  }

  updateList() {
    // custom service request
    this.service.request().subscribe(response) => {
       // backuping in another variable setFirstItemActive fn
       this.singleSelect.matSelect._keyManager.setFirstItemActivebk = this.singleSelect.matSelect._keyManager.setFirstItemActive;
       this.singleSelect.matSelect._keyManager.setFirstItemActive = () => {};
       
       this.options = this.options.concat(response['body']); // updating the options

       // restoring setFirstItemActive fn
       setTimeout(() => {
         this.singleSelect.matSelect._keyManager..setFirstItemActive = this.singleSelect.matSelect._keyManager.setFirstItemActivebk;
       }, 100);
    })
  }
}

I hope someone could figure out a proper way to solve this, in the meanwhile maybe this solution can help someone :)

@raysuelzer
Copy link
Contributor

I'm working on a fix to this.

Essentially, you should only need to call keymanager.setFirstItemActive() if the current firstItem in the options list changes...

@TheParad0X
Copy link

Thank you

@GreyM22
Copy link

GreyM22 commented Jul 14, 2021

I tried the solution to this issue and it didn't work. The solution given by vladimir-poma has syntax error and it seems like he didnt even test it. It didnt work and it keeps scrolling on top. Please can some one tell if there is a way to stop the scrolling of the mat select when you add new data?

@JustinAtanasiu
Copy link

JustinAtanasiu commented Sep 21, 2021

@GreyM22 I just had this problem and I think I found a solution.

Looks like the jump is triggered by adjustScrollTopToFitActiveOptionIntoView:

private adjustScrollTopToFitActiveOptionIntoView(): void {
if (this.matSelect.panel && this.matSelect.options.length > 0) {
const matOptionHeight = this.getMatOptionHeight();
const activeOptionIndex = this.matSelect._keyManager.activeItemIndex || 0;
const labelCount = _countGroupLabelsBeforeOption(activeOptionIndex, this.matSelect.options, this.matSelect.optionGroups);
// If the component is in a MatOption, the activeItemIndex will be offset by one.
const indexOfOptionToFitIntoView = (this.matOption ? -1 : 0) + labelCount + activeOptionIndex;
const currentScrollTop = this.matSelect.panel.nativeElement.scrollTop;
const searchInputHeight = this.innerSelectSearch.nativeElement.offsetHeight
const amountOfVisibleOptions = Math.floor((SELECT_PANEL_MAX_HEIGHT - searchInputHeight) / matOptionHeight);
const indexOfFirstVisibleOption = Math.round((currentScrollTop + searchInputHeight) / matOptionHeight) - 1;
if (indexOfFirstVisibleOption >= indexOfOptionToFitIntoView) {
this.matSelect.panel.nativeElement.scrollTop = indexOfOptionToFitIntoView * matOptionHeight;
} else if (indexOfFirstVisibleOption + amountOfVisibleOptions <= indexOfOptionToFitIntoView) {
this.matSelect.panel.nativeElement.scrollTop = (indexOfOptionToFitIntoView + 1) * matOptionHeight - (SELECT_PANEL_MAX_HEIGHT - searchInputHeight);
}
}
}

This can be solved by calling: this.matSelect._keyManager.setActiveItem(index) after items were added to the list. The index can be computed if you know how many items were in the list prior to adding more.

@lorespaul
Copy link

lorespaul commented Dec 17, 2021

After setting property "disableScrollToActiveOnOptionsChanged" to true on ngx-mat-select-search problem is solved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
9 participants