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;
}
}