How can I make an virtual scroll with angularJS?

João Pedro Segurado picture João Pedro Segurado · Oct 21, 2015 · Viewed 8k times · Source

I'm trying to make a directive that I can do a virtual scroll, so as the user scrolls the table, the table remove "old" views and add "new" views, kind like of collection repeat but I've been failing, I think I didn't understand the math behind it, can someone help me?

this is my directive code:

BaseModule.directive('myScroll', function() {
    return {
        restrict:"A",
        scope:{
            rows:"=",
            headers:"="
        },
        link: function(scope,el) {
            var scrollTop = 0;
            var scrollLeft = 0;
            angular.element(el).on('scroll',function(){
                scrollTop = $(el).scrollTop();
                scrollLeft = $(el).scrollLeft();
                $(el).parent().find(".header").scrollLeft(scrollLeft);
                var height = $(el).height();
                var numberOfRows = height/23;
                var initialRow = scrollTop/23;
                var html = "";
                for(i=0; i<numberOfRows;i++){
                    var row = scope.rows[i+initialRow];
                    html = html + addRow(row,i+initialRow);
                }
                $(el).find('.tbody-scroll').append(html);
            });
            scope.$watch('rows',function(rows){
                var height = $(el).height();
                var numberOfRows = height/23;
                var initialRow = scrollTop/23;
                var html = "";
                for(i=0; i<numberOfRows;i++){
                    var row = rows[i+initialRow];
                    html = html + addRow(row,i+initialRow);
                }
                $(el).find('.tbody-scroll').append(html);
            });
            var addRow = function(row,index){
                var html = "";
                var pos = 0;
                var totalWidth = 0;
                angular.forEach(row,function(col){
                    var width = scope.headers[pos].width;
                    totalWidth = totalWidth + width;
                    html = html + "<span style='width:"+width+"px'>"+col.value+"</span>";
                    pos++;
                });
                html = "<div class='row' style='top:"+index*23+"px;width:"+totalWidth+"px;'>"+html;
                html = html + "</div>";
                return html;
            };
        }
    };
});

<!-- my directive .html -->
<div class="mTable">
    <div class="header" ng-style="headerWidth(headers())">
        <span ng-repeat="header in headers()" ng-style="widthStyle(header)">
            {{::header.label}}
        </span>
    </div>
    <div class="tbody-container" my-scroll headers="headers()" rows="rows()">
        <div class="tbody-scroll" ng-style="scrollHeight(rows(),headers())"></div>
    </div>
</div>

Answer

valepu picture valepu · Oct 21, 2015

Giving a full answer with code might require a bit too much of an effort
This library implements virtual scroll on ng-repeat https://github.com/stackfull/angular-virtual-scroll in the description there's also an article on how to implement this feature on your own.

The basic concept is to make two divs, one above and one below the list, which size is determined by the number of elements inside the list (this approach has a limitation since list elements must either have all the same height or their height must be fixed), then you delete elements as they disappear from the viewport and resize the divs according to the number of elements not currently rendered and your position on the list