Angular virtual scroll: append new items when reaching the end of scroll

18,434

Solution 1

There is a function measureScrollOffset on CdkScrollable that may help. you can subscribe to scroll events and check the offset to determine where you have scrolled.

  ngAfterViewInit(): void {
    this.scrollDispatcher.scrolled().pipe(
      filter(event => this.virtualScroll.measureScrollOffset('bottom') === 0)
    ).subscribe(event => {
      this.searchPageNumber++;
      this.nextSearchPage(this.searchPageNumber); 
    })

And you need to manually call detectChange after scroll item collection change. There's one problem though I have not find a solution: the scroll bar will return to beginning after change detection.

Check the demo here: https://stackblitz.com/edit/template-angular7-material-primeng-ckxgzs

Edit:

Turns out listening for end of scroll is probably not a good idea. A better one is listening for rendering range(I believe it's the actual items rendered in the DOM) change. Once the rendering range ends equals data length, that's the time to add more data to the collection. And this somehow solves the problem of bar going back to beginning.

 ngAfterViewInit(): void {
    this.scrollDispatcher.scrolled().pipe(
      filter(event => this.virtualScroll.getRenderedRange().end === this.virtualScroll.getDataLength())
    ).subscribe(event => {
      this.searchPageNumber++;
      this.nextSearchPage(this.searchPageNumber);
    })

New demo here: https://stackblitz.com/edit/template-angular7-material-primeng-fhikcf

To be honest, I got the idea from: https://angularfirebase.com/lessons/infinite-virtual-scroll-angular-cdk/ You probably can learn more there.

Solution 2

The easiest way to solve this is with the scrolledIndexChange.

<cdk-virtual-scroll-viewport itemSize="500" (scrolledIndexChange)="nextBatch($event)">

$event is the first index of rendered elements at the moment. Therefore, you can call the next page when the index of the first item is near the end of the items in the list

Remember that the items have a fixed height (itemSize) so it is easy to identify the amount of elements that are rendered at a time.

e.g.

  nextBatch(index) {
    if (index + $itemsRenderAtTheMoment === this.items.length - 1) {
      this.page ++;
      this.loadMore();
    }
  }
Share:
18,434
smartmouse
Author by

smartmouse

Considerable experience in web applications development, both as front-end developer and as CMS webmaster. Bitcoin and blockchain enthusiast as writer, speaker and developer of personal projects. An effective communicator with good leadership and analytical skills. Seeking new challenges and responsibilities to progress career. Spare time is for reading news, traveling and working on new ideas...

Updated on June 05, 2022

Comments

  • smartmouse
    smartmouse almost 2 years

    I would like to use virtual scroll on my Angular application. The items in my list are the result of a remote paged search. I would like to load more results (call the next page) every time I reach the end of the viewport scrolling down.

    Here is my template:

    <div class="container">
        <cdk-virtual-scroll-viewport itemSize="100">
            <li *cdkVirtualFor="let item of searchResult">{{item}}</li>
        </cdk-virtual-scroll-viewport>
    </div>
    

    Here is my attempts to make it to work as I need:

    export class SearchComponent {
        @ViewChild(CdkVirtualScrollViewport) virtualScroll: CdkVirtualScrollViewport;
        searchPageNumber: number;
        searchResults: Array<any>;
    
        constructor(private scrollDispatcher: ScrollDispatcher, private searchService: SearchService) {
            this.searchPageNumber = 0;
            this.searchResults = [];
        }
    
        ngOnInit(): void {
            this.nextSearchPage(this.searchPageNumber);
        }
    
        ngAfterViewInit(): void {
            //this.scrollDispatcher.register(this.scrollable);
            //this.scrollDispatcher.scrolled(1000)
            //    .subscribe((viewport: CdkVirtualScrollViewport) => {
            //        console.log('scroll triggered', viewport);
            //    });
    
            this.virtualScroll.renderedRangeStream.subscribe(range => {
                console.log('range', range);
                console.log('range2', this.virtualScroll.getRenderedRange());
                if (this.virtualScroll.getRenderedRange().end % 10 === 0) {
                    this.nextSearchPage(++this.searchPageNumber);
                }
            });
        }
    
        nextSearchPage(pageNumber: number): void {
            this.searchService.getResults(pageNumber).then((pagedResults) => {
                this.searchResults = this.searchResults.concat(pageResuls);
            });
        }
    }
    

    As you can see I can't figure out how to trigger the call to my nextSearchPage function (to load more results) only when the scrollbar reaches the end of the current rendered items in the list.

    Do you know how can I do that?

    Angular scrolling documentation is poor and it lacks of examples, as stated in this issue as well.

  • smartmouse
    smartmouse about 5 years
    Thank you for your reply. What could be the reason that pull the scrollbar at the begin of CdkVirtualScrollViewport?
  • Haijin
    Haijin about 5 years
    That seems due to cdkVirtualFor directive doing a full refresh of all items in the scroll view port. I tried to use a trackBy function to limit the refresh but seems not working.
  • smartmouse
    smartmouse about 5 years
    Thank you for your update. Did you notice that the code within subscribe in the second solution is triggered several times on each scroll instead of just one time? Regarding first solution, it seems to be cleaner, but I can' figure out why change detection has to be forced...
  • smartmouse
    smartmouse about 5 years
    Anyway, your first solution works for me (stackblitz.com/edit/template-angular7-material-primeng-tdrp‌​c3) and in my Angular app I'm not experiencing scrollbar going up everytime new results are appended. So, I mark your answer as solution, thank you so much!
  • Haijin
    Haijin about 5 years
    As to the problem to triggering multiples times, I do see it in the stackblitz demo, but when run on my local computer it seems OK. Change detection has to be forced because angular team decided that scroll events happens too frequently, and trigger a change detection every time would have a big performance hit.
  • AsGoodAsItGets
    AsGoodAsItGets almost 5 years
    I had to add a throttleTime(1000) rxjs operator in the pipe after the filter operator, otherwise it triggers too many requests for data at the same time, as you can notice in your stackblitz as well.
  • BLACKMAMBA
    BLACKMAMBA over 4 years
    this works only when there is only one cdkscroll list, if there are multiple , this events gets triggered if either of them is scrolled
  • mpenaloza
    mpenaloza over 4 years
  • shivani thakur
    shivani thakur over 3 years
    NextPage isn't triggered by changing the screen size.
  • Higgz
    Higgz over 2 years
    the tag works ,thank you!