1

I'm trying to implement Angular Material virtual scrolling, but my items have different sizes. So I must code a custom implementation of VirtualScrollStrategy.

The thing is, I need to use the getRenderedRange() method to get the range of displayed items in the viewport but it's always giving me 0 for start and 0 for end.

Does someone knows why ?

Here's the my implementation of VirtualScrollStrategy :

import {CdkVirtualScrollViewport, VirtualScrollStrategy} from '@angular/cdk/scrolling';
import {Subject} from 'rxjs';
import {distinctUntilChanged} from 'rxjs/internal/operators';
import {FlatRoutingTreeNode} from '../models/routing';
import {Injectable, Optional} from '@angular/core';

export enum NodeHeights {
  CONTEXT_ELEMENT_HEIGHT = 35,
  ROUTING_ITEM_HEIGHT = 37,
  AFTER_FIRST_ENDPOINT_HEIGHT = 32
}

const BUFFER = 1000;

export class RoutingTreeScrollStrategy implements VirtualScrollStrategy {

  public flatNodes: FlatRoutingTreeNode[];

  constructor(private flatNodes: FlatRoutingTreeNode[]) {
    this.flatNodes = flatNodes;
  }

  $index = new Subject<number>();

  scrolledIndexChange = this.$index.pipe(distinctUntilChanged());

  viewport: CdkVirtualScrollViewport;

  attach(viewport: CdkVirtualScrollViewport): void {
    this.viewport = viewport;
    this.viewport.setTotalContentSize(this.getTotalContentSize());
    this.updateRenderedChange();
  }

  detach(): void {
    this.$index.complete();
    delete this.viewport;
  }

  onContentRendered(): void {
  }

  onContentScrolled(): void {
    this.updateRenderedChange();
  }

  onDataLengthChanged(): void {
    if (this.viewport) {
      this.viewport.setTotalContentSize(this.getTotalContentSize());
    }
    this.updateRenderedChange();
  }

  onRenderedOffsetChanged(): void {
  }

  scrollToIndex(index: number, behavior: ScrollBehavior): void {
    if (this.viewport) {
      this.viewport.scrollToOffset(this.getOffsetForIndex(index), behavior);
    }
  }

  nodesChange(flatNodes: FlatRoutingTreeNode[]) {
    this.flatNodes = flatNodes;
    if (this.viewport) {
      this.updateRenderedChange();
      this.viewport.setTotalContentSize(this.getTotalContentSize());
    }
  }

  private updateRenderedChange() {
    if (!this.viewport) { return; }
    const viewportSize = this.viewport.getViewportSize();
    const scrollOffset = this.viewport.measureScrollOffset();
    const { start, end } = this.viewport.getRenderedRange();
    const newRange = { start, end };
    const dataLength = this.viewport.getDataLength();
    const firstVisibleIndex = this.getIndexForOffset(scrollOffset);
    const startBuffer = scrollOffset - this.getOffsetForIndex(start);

    if (startBuffer < BUFFER && start !== 0) {
      newRange.start = Math.max(0, this.getIndexForOffset(scrollOffset - BUFFER * 2));
      newRange.end = Math.min(dataLength, this.getIndexForOffset(scrollOffset + viewportSize + BUFFER));
    } else {
      const endBuffer = this.getOffsetForIndex(end) - scrollOffset - viewportSize;
      if (endBuffer < BUFFER && end !== dataLength) {
        newRange.start = Math.max(0, this.getIndexForOffset(scrollOffset - BUFFER));
        newRange.end = Math.min(dataLength, this.getIndexForOffset(scrollOffset + viewportSize + BUFFER * 2));
      }
    }

    this.viewport.setRenderedRange(newRange);
    this.viewport.setRenderedContentOffset(this.getOffsetForIndex(newRange.start));
    this.$index.next(firstVisibleIndex);
  }

  private getIndexForOffset(scrollOffset: number) {
    let offsetCpt;
    for (let i = 0; i < this.flatNodes.length; i++) {
      if (this.flatNodes[i].expandable) {
        offsetCpt += NodeHeights.CONTEXT_ELEMENT_HEIGHT;
      } else {
        offsetCpt += NodeHeights.ROUTING_ITEM_HEIGHT;
        if (this.flatNodes[i].leaves.length > 0) {
          for (let j = 0; j < this.flatNodes[i].leaves.length; j++) {
            if (j > 0) {
              offsetCpt += NodeHeights.AFTER_FIRST_ENDPOINT_HEIGHT;
            }
          }
        }
      }
      if (offsetCpt >= scrollOffset) {
        return i;
      }
    }
  }

  private getOffsetForIndex(start: number) {
    let offsetCpt;
    for (let i = 0; i < start; i++) {
      if (this.flatNodes[i].expandable) {
        offsetCpt += NodeHeights.CONTEXT_ELEMENT_HEIGHT;
      } else {
        offsetCpt += NodeHeights.ROUTING_ITEM_HEIGHT;
        if (this.flatNodes[i].leaves.length > 0) {
          for (let j = 0; j < this.flatNodes[i].leaves.length; j++) {
            if (j > 0) {
              offsetCpt += NodeHeights.AFTER_FIRST_ENDPOINT_HEIGHT;
            }
          }
        }
      }
    }
    return offsetCpt;
  }

  private getTotalContentSize() {
    let size;
    for (let i = 0; i < this.flatNodes.length; i++) {
      if (this.flatNodes[i].expandable) {
        size += NodeHeights.CONTEXT_ELEMENT_HEIGHT;
      } else {
        size += NodeHeights.ROUTING_ITEM_HEIGHT;
        if (this.flatNodes[i].leaves.length > 0) {
          for (let j = 0; j < this.flatNodes[i].leaves.length; j++) {
            if (j > 0) {
              size += NodeHeights.AFTER_FIRST_ENDPOINT_HEIGHT;
            }
          }
        }
      }
    }
    return size;
  }
}
ssubtil
  • 11
  • 2

0 Answers0