[Solved] NgbTypeahead component doesn’t scroll inside a scrollable component

I am using NgbTypeahead component of ng-bootstrap. My problem is, when I put the typeahead component inside a scrollable component and make a scroll down, the position of dropdown container doesn’t change.

<div style="height: 300px;   overflow-y: auto;">
...
<input id="typeahead-template" type="text" class="form-control [(ngModel)]="model" 
       [ngbTypeahead]="search" [resultTemplate]="rt [inputFormatter]="formatter" />
...
</div>

It could be a small CSS issue but I could not find the solution.

Here is the plunkr :
http://plnkr.co/edit/rxOhDy72YWlLy9U4Ujcd?p=preview

Type in a character in the text box and then scroll up-down

Solution #1:

To add a vertical scroll bar to typeahead results, you can use something this:

ngb-typeahead-window.dropdown-menu {
    max-height: 500px !important;
    overflow-y: auto;
}
Respondent: Chris Tapay

Solution #2:

As NgbTypeahead does not have support with scroll, we need to handle from component. Use showDropdownEle function on keydown of Input.

private isElementInViewport(el, inputElem) {
const rect = el.getBoundingClientRect();
const rectElem = inputElem.getBoundingClientRect();
console.log(rectElem);
return (
  rect.top >= rectElem.bottom &&
  rect.left >= 0 &&
  rect.bottom <= (rectElem.bottom + rect.offsetHeight) &&
  rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}

public showDropdownEle(event) {
if (event.keyCode === 38 || event.keyCode === 40) {
  if (event.target.nextElementSibling && event.target.nextElementSibling.nodeName === 'NGB-TYPEAHEAD-WINDOW') {
    let activeDropdownEle = (event.keyCode === 40) ? event.target.nextElementSibling.querySelector('.active').nextElementSibling : event.target.nextElementSibling.querySelector('.active').previousElementSibling;
    if (!activeDropdownEle) {
      const allDropdownElems = event.target.nextElementSibling.querySelectorAll('.dropdown-item');
      activeDropdownEle = (event.keyCode === 38) ? allDropdownElems[allDropdownElems.length - 1] : allDropdownElems[0];
    }
    if (!this.isElementInViewport(activeDropdownEle, event.target) && event.keyCode === 40) {
      activeDropdownEle.scrollIntoView(false);
    }
    if (!this.isElementInViewport(activeDropdownEle, event.target) && event.keyCode === 38) {
      activeDropdownEle.scrollIntoView(true);
    }
  }
}
}
Respondent: Deepak Reddy

Solution #3:

Place the following code in your styles.css.

We can place the following code in any of the following files

  • Style.css file
  • index.html style tag
ngb-typeahead-window {
  max-height: 200px;
  overflow-y: auto;
  overflow-x: hidden;
}

Working code stackblitz link: https://stackblitz.com/edit/angular-qpzsfv

Respondent: likith sai

Solution #4:

typeahead-scrollable.html file

<input id="typeahead-scrollable" type="text" class="form-control" (keydown)="typeaheadKeydown($event)" #typeaheadInstance="ngbTypeahead" [(ngModel)]="model" [ngbTypeahead]="search" [resultFormatter]="formatter" 

typeahead-scrollable.ts file

    @ViewChild('typeaheadInstance')
    private typeaheadInstance: NgbTypeahead;

    typeaheadKeydown($event: KeyboardEvent) {
      if (this.typeaheadInstance.isPopupOpen()) {
        setTimeout(() => {
          const popup = document.getElementById(this.typeaheadInstance.popupId);
          const activeElements = popup.getElementsByClassName('active');
          if (activeElements.length === 1) {
            // activeElements[0].scrollIntoView();
            const elem = (activeElements[0] as any);
            if (typeof elem.scrollIntoViewIfNeeded === 'function') {
              // non standard function, but works (in chrome)...
              elem.scrollIntoViewIfNeeded();
            } else {
              //do custom scroll calculation or use jQuery Plugin or ...
              this.scrollIntoViewIfNeededPolyfill(elem as HTMLElement);
            }
          }
        });
      }
    }

    private scrollIntoViewIfNeededPolyfill(elem: HTMLElement, centerIfNeeded = true) {
      var parent = elem.parentElement,
        parentComputedStyle = window.getComputedStyle(parent, null),
        parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
        parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
        overTop = elem.offsetTop - parent.offsetTop < parent.scrollTop,
        overBottom = (elem.offsetTop - parent.offsetTop + elem.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
        overLeft = elem.offsetLeft - parent.offsetLeft < parent.scrollLeft,
        overRight = (elem.offsetLeft - parent.offsetLeft + elem.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
        alignWithTop = overTop && !overBottom;

      if ((overTop || overBottom) && centerIfNeeded) {
        parent.scrollTop = elem.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + elem.clientHeight / 2;
      }

      if ((overLeft || overRight) && centerIfNeeded) {
        parent.scrollLeft = elem.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + elem.clientWidth / 2;
      }

      if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
        elem.scrollIntoView(alignWithTop);
      }
    }

Working example:

https://stackblitz.com/edit/angular-utd9ii?file=app%2Ftypeahead-scrollable.ts

Respondent: pka246

The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .

Leave a Reply

Your email address will not be published.