angularangular-materialangular-cdkangular-cdk-virtual-scroll

Maintain scroll position with CDK autosize virtual scroll strategy


Maintain scroll position with CDK autosize virtual scroll strategy

I have a large list of items that can be scrolled with <cdk-virtual-scroll-viewport autosize> provided by @angular/cdk-experimental (the items have dynamic heights, thus I'm using this scroll strategy instead of the FixedSizeVirtualScrollStrategy).

New items are inserted in the list over time, i.e. they are appended on top. When the user scrolls down I want to avoid that new items will push the items in the viewport away. Therefore I need a mechanism to maintain / restore the scroll position after the items have been added.

I have a semi-working solution (and hacky because it reads private fields), but the viewport shifts a few pixels randomly after items have been added.

Here are the relevant code parts:

@ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport;

...

// After new items have been added:

const offset = this.viewport.measureScrollOffset('top');
const topPosition = offset + newItems.length * this.viewport['_scrollStrategy']._averager._averageItemSize; // the autosize strategy determines an average item size I'm trying to use to determine how the viewport should be shifted

this.viewport.scrollTo({
  top: topPosition
});

I created a demo of this approach here: https://stackblitz.com/edit/angular-be926g?file=src/app/cdk-virtual-scroll-overview-example.ts

Any ideas how to achieve this seamlessly with a constant viewport?


Solution

  • you can use this.viewport.getRenderedRange() to check the viewport is on top and if zero then append the elements else keep the elements in a bucket and append the objects when user scrolled to top of viewport.

      const range = this.viewport.getRenderedRange();
      console.log("range", range);
      bucket = [...bucket, ...newItems];
      console.log("bucket", bucket);
      if (range.start == 0) {
        this.items = [...bucket.reverse(), ...this.items];
        bucket = [];
      }
    

    stackblitz example