Search code examples
angularangular-materialangular7

Angular Material VirtualScrollViewPort - load data async


I find different samples for the VirtualScrollViewPort but with all I have the problem how to use them in real life. The samples load the whole data at once from the server - but since they are too large, I wanna load them individually. My main requirements are:

  1. Search mask - user enters some data
  2. Display progress bar...
  3. Query a Search on the server
  4. If no result is found ==> display a message
  5. If something is found ==> display the first n (=10) items
  6. After the user scrolls down and only e.g. 5 items are left ==> load additionally 10 items
    • continue with 5.
    • if only e.g. 3 are left ==> end the scrolling

I tried already the approach from Specifying data section - but there I fail how to recognize that no data is loaded AND I fail to initiate the view - especially when the user resets the content.

I tried as well with

<cdk-virtual-scroll-viewport itemSize="itemHeight" (scrolledIndexChange)="nextBatch($event,(resultList[resultList.length-1].total) )"
class="scroll-container">
  <div *cdkVirtualFor="let search of resultList"  class="card-item" >

which works for the first requirements but finally I fail with scrolledIndexChange is fired only on the very first item on the list. I have no clue how to track if user already is displaying already item 6 (which would load additional data). On the API page I do not see any @Output() beside the scrolledIndexChange.

Any hint how to track the events properly?

UPDATE First problem I figured out was the incorrect syntax of setting the height, i.e. [itemSize]="itemHeight" is the appropriate syntax otherwise it remains always to zero ==> all elements are rendered!


Solution

  • After some work my final solution looks like:

    <ng-container *ngIf="lstSearchResults|async as resultList; else searching">
        <cdk-virtual-scroll-viewport [itemSize]="itemHeight" (scrolledIndexChange)="nextBatch()"
             class="scroll-container">
           <div *cdkVirtualFor="let search of resultList"  class="card-item">
    

    where it is noteworthy that my list is an async list, named lstSearchResults and in the the ts code I have:

    // for proper height and caching... (in pixels)
    itemHeight = 174;
    
    search(searchConfig: SearchOptions): void {
        //    ....
        this.lstSearchResults = new BehaviorSubject<SearchResult[]>(null);
        // call the REST service
        this.searchService.doSearch(searchConfig).subscribe((foundEntry) => {
            if (!this.resultList) {
                // first list - nothing found up till now
                this.resultList = foundEntry;
            } else {
                if (!this.resultList[this.resultList.length - 1]) {
                    //remove the marker (which was added below/previously)
                    this.resultList.pop();
                }
                foundEntry.map((item) => this.resultList.push(item));
            }
            if (this.resultList[0] && this.resultList[0].total > this.resultList.length + 1) {
                //some more elements could be fetched from the server ==> add a dummy entry for rendering
                this.resultList.push(undefined);
            }
            // notify the search list to be updated
            this.lstSearchResults.next(this.resultList);
        });
    }
    

    and for the scrolling I have the following code:

    nextBatch(): void {
        if (this.theEnd) {
            return;
        }
    
        if (this.resultList[0]) {
            // something was found
            if (
                this.viewport.getRenderedRange().end ===
                this.viewport.getDataLength()
            ) {
                // since we scrolled to the very end of the rendered display
                // ==> check if further search is required (and do so...)
                const searchTotal = this.resultList[0].total;
                this.mySearchConfig.posOffset += this.mySearchConfig.noOfElements;
                // some basic check if the total counter exceeds the current offset
                // i.e. no further search required
                if (this.mySearchConfig.posOffset <= searchTotal) {
                    this.search(this.mySearchConfig, true);
                } else {
                    this.theEnd = true;
                }
            }
        } else {
            // nothing found ==> mark the end
            this.theEnd = true;
        }
    }