Skip to content

autocomplete does not stick when scrolling #10079

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

Open
weijyewang opened this issue Feb 22, 2018 · 47 comments
Open

autocomplete does not stick when scrolling #10079

weijyewang opened this issue Feb 22, 2018 · 47 comments
Labels
area: cdk/scrolling area: material/autocomplete P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@weijyewang
Copy link

Bug, feature request, or proposal:

Bug

What is the expected behavior?

The autocomplete drop down list should stick to the bottom of input element when scrolling

What is the current behavior?

The drop down list is sticky to position on the screen, it will not follow the input element position when scrolling

What are the steps to reproduce?

image

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

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

Windows 10 64 bit
Chrome Version 64.0.3282.167 (Official Build) (64-bit)
Angular 5.1.2
Angular Material 5.0.2
TypeScript 2.7.1

Is there anything else we should know?

@crisbeto
Copy link
Member

That's because, by default, Material won't listen to scroll events on all elements. Is your main scroll container something different than the body?

@weijyewang
Copy link
Author

@crisbeto yes, the scroll container is actually mat-sidenav-content element, descendant of the body element, which has overflow: auto set into it. Is there a way to configure the event listener to listen to the element of choosing?

@crisbeto
Copy link
Member

Usually you'd have to add the cdkScrollable attribute to your scroll container so the CDK can pick it up, however the sidenav container has it set up already. Can you try adding the ScrollDispatchModule from @angular/cdk/scrolling to your imports?

@jelbourn jelbourn added the needs: clarification The issue does not contain enough information for the team to determine if it is a real bug label Mar 1, 2018
@karan-kang
Copy link

karan-kang commented Apr 20, 2018

I have noticed that the mat-select does not have this issue, as it blocks scroll action when the panel is opened.

May be autocomplete should follow the same approach.

@ezo5
Copy link

ezo5 commented May 17, 2018

Having same use case as @weijyewang with ScrollDispatchModule inported. As @Karankang007 mentioned blocking scroll as mentioned here would be fair enough temporary fix. Tried it with MAT_AUTOCOMPLETE_SCROLL_STRATEGY, no luck.

@NithinBiliya
Copy link

NithinBiliya commented Dec 11, 2018

Having same issue when the main scroll container is content of dialog popup (mat-dialog-content). Ideally it should work similar to mat-select.

@israelpereira
Copy link

I was with the same problem, I used the workaround described in this issue:

#7897

@NithinBiliya
Copy link

NithinBiliya commented Dec 15, 2018

@israelpereira #7897 did help. On using cdkScrollable the dropdown section is sticking to the input, but it is no more contained within the dialog content section.

Reproduced on stackblitz. Click on the 'open popup' button and scroll the dialog section.

I think the ideal solution would be to disable scroll when the dropdown is opened as suggested by @Karankang007 in comment.

@israelpereira
Copy link

I had the same issue.. to solve it.. I found something called ScrollStrategy and I used the following code:

export function scrollFactory(overlay: Overlay): () => CloseScrollStrategy {
	return () => overlay.scrollStrategies.close();
}

@NgModule({
	imports: [modules],
	exports: [modules],
	providers: [
		{ provide: MAT_AUTOCOMPLETE_SCROLL_STRATEGY, useFactory: scrollFactory, deps: [Overlay] }
	]
})
export class AppModule {}

It closes the autocomplete box when it identifies the scroll outside the autocomplete.

hope it helps.

@NithinBiliya
Copy link

thanks @israelpereira. this helps. The dropdown overlay closes on scroll. The only side effect is that to open the dropdown overlay once again, the focus has to be taken out of the autocomplete input and back into it.

Behaviour similar to mat-select could be achieved if BlockScrollStrategy worked, but sadly it doesn't.

@israelpereira
Copy link

Welcome, I had this issue too...

@ViewChild('searchInput', { read: MatAutocompleteTrigger })
triggerAutocompleteInput: MatAutocompleteTrigger;

openAutocompletePanel(){
this.triggerAutocompleteInput.openPanel();
}

//html
<input  (click)="openAutocompletePanel()" />

add this code to the component and put the (click)="openAutocompletePanel()" in the input field, it will solves the problem.

I don't know if it is the best solution, probably not, but worked .. I was losing so much time on it..

@NithinBiliya
Copy link

@israelpereira thanks for MatAutocompleteTrigger.openPanel() for opening the dropdown section

@omaracrystal
Copy link

omaracrystal commented May 12, 2019

Solution:

import { ScrollingModule } from '@angular/cdk/scrolling';

Locate the Modal that has this component. Place the cdkScrollable directive on the outer most div of that Modal... ie:

<div class="full-page-takeover" cdkScrollable> {{ modal content }} </div>

This worked for me.

@YasinK123
Copy link

just add below code to your autocomplete function and it will work

appendTo: $('#tag').parent(),

this will stick the autocomplete list to its parent textbox

@Prakashpb
Copy link

Is there any solution for mat-autocomplete not sticking to the input when scrolled.

Note: None of the given solution worked for me

@yantrab
Copy link

yantrab commented Aug 27, 2019

@omaracrystal solution work for me, but then , i have problem with z-index.

image

image

@HiagoMM
Copy link

HiagoMM commented Oct 21, 2019

image
i have the same problem

@fzs1994
Copy link

fzs1994 commented Dec 24, 2019

I'm having the same issue when I used autocomplete inside mat-side-nav

@matiasfs12
Copy link

matiasfs12 commented Jan 28, 2020

Hey @weijyewang, @fzs1994 I'm in the same scenario, did you manage to solve it ? None of the given solution worked for me.

@weijyewang
Copy link
Author

@matiasfs12 I just added cdkScrollable to the parent element just like what @crisbeto and @omaracrystal suggested. I am happy to help if you can provide a stackblitz https://stackblitz.com/edit/angular

@matiasfs12
Copy link

matiasfs12 commented Jan 28, 2020

@matiasfs12 I just added cdkScrollable to the parent element just like what @crisbeto and @omaracrystal suggested. I am happy to help if you can provide a stackblitz https://stackblitz.com/edit/angular

That doesn't work for me since i'm not using mat-sidenav-container, nor mat-sidenav-content, actually i'm putting the cdkScrollable on mat-sidenav tag, but the scroll listener it's not even firing up because there is an intermediate element mat-drawer-inner-container which actually scroll up/down the content, and i cannot disable the scroll in this element with CSS.

image

In any case, i will try to do a stackblitz, thanks in advance!

@weijyewang
Copy link
Author

@matiasfs12 you just have to set cdkScrollable to the parent container that is scrollable. It can be any elements like div. If mat-sidenav is giving you the problem maybe you can implement a sidenav on your own? This is how I use cdkScrollable in my code

image

@matiasfs12
Copy link

matiasfs12 commented Jan 30, 2020

Hey @weijyewang , Finally managed to get it working, I had to append a <mat-sidenav-content> into <mat-sidenav>, setting cdkScrollable on it, and some hacky CSS styles, thx mate

@rahulkathar
Copy link

All the above solutions not worked for me, Please anyone has the best solution.
I have used Angular material 8.2.3 version with Angular 8.

@SurabhiPal0892
Copy link

SurabhiPal0892 commented Mar 5, 2020

The above solution works. you just have to find the class where you are defining the overflow property to scroll this will give you the element which is controlling the scrolling of the page. Then add cdkScrollable to that element. And add this in your module. since ScrollDispatchModule has been renamed to ScrollingModule.
import { ScrollingModule } from '@angular/cdk/scrolling';

@panyann
Copy link

panyann commented Apr 24, 2020

Nothing worked for me.

@omaracrystal
Copy link

omaracrystal commented Apr 25, 2020

@panyann If you have a autocomplete within a mat dialog - this is how I solved this issue.

  1. Wrap all your content with <mat-dialog-content></mat-dialog-content>
  2. Place the directive cdkScrollable on that ^
<mat-dialog-content cdkScrollable>...</mat-dialog-content>
  1. In scss target .mat-dialog-content and give it a max-height of 100vh
.mat-dialog-content {
    width: 100%;
    height: 100%;
    max-height: 100vh;
  }

@panyann
Copy link

panyann commented Apr 27, 2020

@omaracrystal I don't have a dialog and cdkScrollable seems to not work whatever I do!
So I made a workaround and I will share with everyone...

First of all we need to be able to use autoComplete methods, so we must take this control from the view. Add the id: #autoCompleteInput

<input
        #autoCompleteInput
        type="text"
        class="form-control"
        matInput
        [matAutocomplete]="auto"
        formControlName="country"
        (input)="filterCountries($event.target.value)"
      />

In the component:

@ViewChild('autoCompleteInput', { read: MatAutocompleteTrigger })
  autoComplete: MatAutocompleteTrigger;

Now we have autoComplete as a variable. Next we need a scrolling event:

ngOnInit(): void {
    window.addEventListener('scroll', this.scrollEvent, true);
}

And finally add a function to the component:

scrollEvent = (event: any): void => {
    if(this.autoComplete.panelOpen)
      // this.autoComplete.closePanel();
      this.autoComplete.updatePosition();
};

You can set it to close the panel or update its position. Problem solved, but is not perfect if you have a lot of these autoComplete elements.

I can only wonder why this position update does not happen automatically and we all need to make idiots out of ourselves and waste time for finding solution...

@Rakshithkakathkar
Copy link

Thank you @panyann

This is exactly what I wanted

@TejashreeD
Copy link

TejashreeD commented Jun 19, 2020

@panyann Thanks a lot! your solution worked 💯 . 🥇

"@angular/animations": "~9.0.6", "@angular/cdk": "^9.2.4", "@angular/common": "~9.0.6", "@angular/compiler":"~9.0.6",
"@angular/core": "~9.0.6",
"@angular/flex-layout": "^9.0.0-beta.31",
"@angular/forms": "~9.0.6",
"@angular/material": "^9.2.4",
"@angular/platform-browser": "~9.0.6",
"@angular/platform-browser-dynamic": "~9.0.6"`

@TrevorKarjanis
Copy link
Contributor

TrevorKarjanis commented Jul 2, 2020

For @matiasfs12 and others with overlays in mat-sidenav or drawer, CdkScrollable is declared on mat-sidenav-container but not mat-sidenav. Additionally, as was pointed out mat-sidenav has an inner div element which handles overflow and can't be accessed. Place the following in your global styles, and declare CdkScrollable on the mat-sidenav.

.mat-drawer, .mat-sidenav {
  &[cdk-scrollable], &[cdkScrollable] {
    .mat-drawer-inner-container {
      overflow: visible;
    }
  }
}

This issue affects more than autocomplete, so I submitted a separate issue #19846. There is a similar issue with tabs #8405.

@matiasfs12
Copy link

For @matiasfs12 and others with overlays in mat-sidenav or drawer, CdkScrollable is declared on mat-sidenav-container but not mat-sidenav. Additionally, as was pointed out mat-sidenav has an inner div element which handles overflow and can't be accessed. Place the following in your global styles, and declare CdkScrollable on the mat-sidenav.

.mat-drawer, .mat-sidenav {
  &[cdk-scrollable], &[cdkScrollable] .mat-drawer-inner-container {
    overflow: visible;
  }
}

This issue affects more than autocomplete, so I submitted a separate issue #19846. There is a similar issue with tabs #8405.

Thank You @TrevorKarjanis

@rahulkathar
Copy link

For @matiasfs12 and others with overlays in mat-sidenav or drawer, CdkScrollable is declared on mat-sidenav-container but not mat-sidenav. Additionally, as was pointed out mat-sidenav has an inner div element which handles overflow and can't be accessed. Place the following in your global styles, and declare CdkScrollable on the mat-sidenav.

.mat-drawer, .mat-sidenav {
  &[cdk-scrollable], &[cdkScrollable] {
    .mat-drawer-inner-container {
      overflow: visible;
    }
  }
}

This issue affects more than autocomplete, so I submitted a separate issue #19846. There is a similar issue with tabs #8405.

@TrevorKarjanis grate man... it is a very excellent and proper solution...

@wagnermaciel wagnermaciel added needs triage This issue needs to be triaged by the team and removed needs: clarification The issue does not contain enough information for the team to determine if it is a real bug labels Aug 13, 2020
@mmalerba mmalerba added has pr 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 Aug 14, 2020
@mmalerba
Copy link
Contributor

This is most likely caused by #19846 (which should be fixed by #19848). We should confirm after the fix goes in that it resolves this issue.

@janvanrossum-bookzo
Copy link

janvanrossum-bookzo commented Dec 23, 2020

@omaracrystal I don't have a dialog and cdkScrollable seems to not work whatever I do!
So I made a workaround and I will share with everyone...

First of all we need to be able to use autoComplete methods, so we must take this control from the view. Add the id: #autoCompleteInput

<input
        #autoCompleteInput
        type="text"
        class="form-control"
        matInput
        [matAutocomplete]="auto"
        formControlName="country"
        (input)="filterCountries($event.target.value)"
      />

In the component:

@ViewChild('autoCompleteInput', { read: MatAutocompleteTrigger })
  autoComplete: MatAutocompleteTrigger;

Now we have autoComplete as a variable. Next we need a scrolling event:

ngOnInit(): void {
    window.addEventListener('scroll', this.scrollEvent, true);
}

And finally add a function to the component:

scrollEvent = (event: any): void => {
    if(this.autoComplete.panelOpen)
      // this.autoComplete.closePanel();
      this.autoComplete.updatePosition();
};

You can set it to close the panel or update its position. Problem solved, but is not perfect if you have a lot of these autoComplete elements.

I can only wonder why this position update does not happen automatically and we all need to make idiots out of ourselves and waste time for finding solution...

I've transformed this into a directive so you don't have to include this code in every component:

import { Directive, Input, OnDestroy } from '@angular/core';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';

@Directive({
  selector: '[autocompletePosition]'
})
export class AutocompletePositionDirective implements OnDestroy {
  private matAutocompleteTrigger: MatAutocompleteTrigger;

  @Input() set autocompletePosition(value: MatAutocompleteTrigger) {
    this.matAutocompleteTrigger = value;
    window.addEventListener('scroll', this.scrollEvent, true);
  }

  private scrollEvent = (): void => {
    if (this.matAutocompleteTrigger == null) {
      return;
    }
    if (this.matAutocompleteTrigger.panelOpen) {
      this.matAutocompleteTrigger.updatePosition();
    }
  };

  ngOnDestroy() {
    window.removeEventListener('scroll', this.scrollEvent, true);
  }
}

In your input element, include these last two attributes (#trigger and [autocompletePosition] to activate the directive:
<input matInput type="text" [matAutocomplete]="autoComplete" #trigger="matAutocompleteTrigger" [autocompletePosition]="trigger">

And yes, I also wonder why this is not fixed in the library itself. There are a lot of bug reports about this and sprinkling cdkScrollable over your components does not look like a good pattern to me...

@itsmerockingagain
Copy link

@israelpereira #7897 did help. On using cdkScrollable the dropdown section is sticking to the input, but it is no more contained within the dialog content section.

Reproduced on stackblitz. Click on the 'open popup' button and scroll the dialog section.

I think the ideal solution would be to disable scroll when the dropdown is opened as suggested by @Karankang007 in comment.

@NithinBiliya Any luck in solving the issue i.e auto-select goes out of the scrollable area?

@MAbidArif
Copy link

Hi everyone, I also have the issue of autocomplete drop down issue while scrolling. I do within the rails application. Kindly suggest me the best solution for this!!

Screenshot from 2021-09-20 19-08-13

@pavel-romanov8
Copy link

pavel-romanov8 commented Oct 20, 2021

With @dbitkovski we came up to slightly different solution for directive that @janvanrossum-bookzo mentioned. Instead of passing trigger trough the input we just take it from DI. Also we apply this directive to all matAutocomplete inputs, so you don't need to provide it manually every time.

@Directive({
  selector: 'input[matAutocomplete]',
})
export class AutocompletePositionDirective implements OnDestroy {

  public constructor(
    private readonly matAutocompleteTrigger: MatAutocompleteTrigger,
  ) {
    window.addEventListener('scroll', this.scrollEvent, true);
  }

  public ngOnDestroy(): void {
    window.removeEventListener('scroll', this.scrollEvent, true);
  }

  private scrollEvent = (): void => {
    if (this.matAutocompleteTrigger == null) {
      return;
    }
    if (this.matAutocompleteTrigger.panelOpen) {
      this.matAutocompleteTrigger.updatePosition();
    }
  };
}

@gamble4846
Copy link

I've created a work around using (scroll)

app.component.html -

<div class="inner-content" (scroll)="SetupOnScroll($event,routerOutletContainer)" #routerOutletContainer>
     <router-outlet></router-outlet>
</div>

app.component.ts -

SetupOnScroll(event:any,routerOutletContainer:any){
  let overlaypanes:any = document.getElementsByClassName("cdk-overlay-pane");
  let amountScrolled = this.lastScrollTop - routerOutletContainer.scrollTop;
  this.lastScrollTop = routerOutletContainer.scrollTop;
  if(overlaypanes.length > 0){
    for (let index = 0; index < overlaypanes.length; index++) {
      const overlaypane = overlaypanes[index];
      const paneTop = overlaypane.style.top;
      const paneBottom = overlaypane.style.bottom;
      if(paneTop != 0){
        overlaypane.style.top = Number(paneTop.replace("px", "")) + amountScrolled +"px";
      }
      if(paneBottom != 0){
        overlaypane.style.bottom = Number(paneBottom.replace("px", "")) + (amountScrolled * -1) +"px";
      }
    }
  }
}

This would update the top or bottom of the currently opened cdk-overlay-pane according to the scroll value

This is working for select and date picker as well

@mpilarcastillejo
Copy link

mpilarcastillejo commented Apr 26, 2023

Para solucionar el problema de autocomplete superpuesto a otros elementos, mejor es que al detectar el scroll este se cierre, y asi se evitara este encima de los demas elementos cuando este dentro de un element con scroll, entonces para eso aplicaria lo mismo que @pavel-romanov8 con una ligera modificacion para cerrarlo cuando se detecte scroll, obviando el scroll del autocomplete, espero le sirva a alguien.

import { Directive, OnDestroy } from '@angular/core';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';

@Directive({
	selector: '[appAutocompletePosition]'
})
export class AutocompletePositionDirective implements OnDestroy {
	public constructor(private readonly matAutocompleteTrigger: MatAutocompleteTrigger) {
		window.addEventListener('scroll', this.scrollParent.bind(this), true);
	}

	public ngOnDestroy(): void {
		window.removeEventListener('scroll', this.scrollParent, true);
	}

	private scrollParent(event: Event) {
		if (event.target != document.getElementsByClassName('mat-autocomplete-panel')[0]) {
			if (this.matAutocompleteTrigger == null) {
				return;
			}
			if (this.matAutocompleteTrigger.panelOpen) {
				this.matAutocompleteTrigger.closePanel();
			}
		}
	}
}

@chitgoks
Copy link

@omaracrystal I don't have a dialog and cdkScrollable seems to not work whatever I do! So I made a workaround and I will share with everyone...

First of all we need to be able to use autoComplete methods, so we must take this control from the view. Add the id: #autoCompleteInput

<input
        #autoCompleteInput
        type="text"
        class="form-control"
        matInput
        [matAutocomplete]="auto"
        formControlName="country"
        (input)="filterCountries($event.target.value)"
      />

In the component:

@ViewChild('autoCompleteInput', { read: MatAutocompleteTrigger })
  autoComplete: MatAutocompleteTrigger;

Now we have autoComplete as a variable. Next we need a scrolling event:

ngOnInit(): void {
    window.addEventListener('scroll', this.scrollEvent, true);
}

And finally add a function to the component:

scrollEvent = (event: any): void => {
    if(this.autoComplete.panelOpen)
      // this.autoComplete.closePanel();
      this.autoComplete.updatePosition();
};

You can set it to close the panel or update its position. Problem solved, but is not perfect if you have a lot of these autoComplete elements.

I can only wonder why this position update does not happen automatically and we all need to make idiots out of ourselves and waste time for finding solution...

I can confirm this works. however, when used inside a mat-table it does not work. or is there a different way to do so?

@panyann
Copy link

panyann commented Dec 14, 2023

@chitgoks Sorry, I can't help you, but for sure there is some workaround, like always. The real question is why it's not fixed after more than 5 years of reporting the issue...

@ianwesterfield
Copy link

ianwesterfield commented Apr 15, 2024

@panyann

Agreed - this sort of lackadaisical response to fairly important issues (where UI/X is involved) cramps my style. And honsestly, the grass is just as brown in any other yard...

I've begun migrating to custom controls from stock. Once I started to saw away at the umbilical, I realized Angular already has all I need to make some great controls. Then any issues are mine and I can resolve them at the root level instead of overrides or other libraries to plug the holes.

@chitgoks
Copy link

chitgoks commented Apr 15, 2024

I got this to work in the mat-table. I made it as another custom component using that solution.

@DAlexOffcial
Copy link

thanks @panyann

@Akash280899
Copy link

I am also facing the same issue when I try to open a mat-autocomplete dropdown inside a mat-dialog. Dropdown is not sticking to the input. Any solution for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: cdk/scrolling area: material/autocomplete 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