Search code examples
javascriptangularangular-materialvirtualscrollangular-cdk-virtual-scroll

Scroll to bottom with cdk-virtual-scroll (Angular 8)


Goal

Display a list of messages and scroll to the bottom when a new message is received, even when I am at the top. I would like to scroll fully bottom even with elements of different heights.

Problem

With virtual scroll, I have to set the [itemSize] property, but for me it is not a static value:

  • When a message is too long for one line it breaks in multiple, so its height changes.
  • I have different types of messages with different heights (system messages for example).

Also, I am using ng-content to insert a button from the parent to load previous messages. What I see is that, when _scrollToBottom is invoked, instead of taking me to the bottom, it takes me to a bit higher. I suspect this is because of the different heights of elements inside virtual-scroll.

I have read this autosize scroll strategy issue from Angular: https://github.com/angular/components/issues/10113; but I am not sure this will solve my problem.

Any idea of what I could do will be welcome.

Test

Codesandbox: https://codesandbox.io/s/angular-virtual-scroll-biwn6

  • When messages are loaded, scroll up.
  • Send message. (When the new message is loaded, instead of scrolling to bottom, the virtual-scroll stops a little higher)
  • Repeat

Video with the error: https://gofile.io/d/8NG9HD


Solution

The solution given by Gourav Garg works. Simply by executing twice the scroll function.

I am doing this now:


  private _scrollToBottom() {
    setTimeout(() => {
      this.virtualScrollViewport.scrollTo({
        bottom: 0,
        behavior: 'auto',
      });
    }, 0);
    setTimeout(() => {
      this.virtualScrollViewport.scrollTo({
        bottom: 0,
        behavior: 'auto',
      });
    }, 50);
  }

I think it is not very elegant but works fine.


Solution

  • You can use if you are using cdk > 7

    this.virtualScrollViewport.scrollToIndex(messages.length-1);
    

    As this will move to the top of last item. You need to call scrollIntoView for that item.

    this.virtualScrollViewport.scrollToIndex(this.numbers.length - 1);
    setTimeout(() => {
      const items = document.getElementsByClassName("list-item");
      items[items.length - 1].scrollIntoView();
    }, 10);
    <cdk-virtual-scroll-viewport #virtualScroll style="height: 500px" itemSize="90">
      <ng-container *cdkVirtualFor="let n of numbers">
        <li class="list-item"> {{n}} </li>
      </ng-container>
    </cdk-virtual-scroll-viewport>

    I have updated your sandbox