import { CdkVirtualScrollViewport, VirtualScrollStrategy, } from '@angular/cdk/scrolling'; import { Inject, Injectable } from '@angular/core'; import { Subject } from 'rxjs'; import { distinctUntilChanged } from 'rxjs/operators'; // services import { CalendarDateTimePickerService } from '../services/calendar-datetime-picker.service'; export const STARTING_YEAR = new Date().getFullYear() - 90; export const RANGE = 100 * 12; const BUFFER = 500; @Injectable() export class CalendarStrategy implements VirtualScrollStrategy { constructor( private calendarService: CalendarDateTimePickerService, @Inject(String) private startedHeight: number, @Inject(String) private FULL_SIZE: number, @Inject(String) private SCROLL_TYPE: string ) {} private index$ = new Subject(); scrolledIndexChange = this.index$.pipe(distinctUntilChanged()); private viewport?: CdkVirtualScrollViewport; public attach(viewport: CdkVirtualScrollViewport): void { this.viewport = viewport; this.viewport.setTotalContentSize(this.startedHeight); this.updateRenderedRange(this.viewport); } public updateScrollHeights(height: number): void { this.viewport?.setTotalContentSize(height); this.updateRenderedRange(this.viewport); } public detach(): void { this.index$.complete(); this.viewport = undefined; } public onContentScrolled(): void { if (this.viewport) this.updateRenderedRange(this.viewport); } /** These do not matter for this case */ public onDataLengthChanged(): void {} public onContentRendered(): void {} public onRenderedOffsetChanged(): void {} public scrollToIndex(index: number, behavior: ScrollBehavior): void { if (this.viewport) this.viewport.scrollToOffset(this.getOffsetForIndex(index), behavior); } public scrollToOffset(offset: number, behavior: ScrollBehavior): void { if (this.viewport) this.viewport.scrollToOffset(offset, behavior); } public getOffsetForIndex(index: number): number { return this.FULL_SIZE * index; } private getIndexForOffset(offset: number): number { return Math.round(offset / this.FULL_SIZE); } private updateRenderedRange(viewport?: CdkVirtualScrollViewport): void { if (!viewport) return; const offset = viewport.measureScrollOffset(); const { start, end } = viewport.getRenderedRange(); const viewportSize = 230; const dataLength = viewport.getDataLength(); const newRange = { start, end }; const firstVisibleIndex = this.getIndexForOffset(offset); const startOffsetIndex = this.FULL_SIZE * start; const startBuffer = offset - startOffsetIndex; if (startBuffer < BUFFER && start !== 0) { newRange.start = Math.max(0, this.getIndexForOffset(offset - BUFFER * 2)); newRange.end = Math.min( dataLength, this.getIndexForOffset(offset + viewportSize + BUFFER) ); } else { const endBuffer = this.FULL_SIZE * end - offset - viewportSize; if (endBuffer < BUFFER && end !== dataLength) { newRange.start = Math.max(0, this.getIndexForOffset(offset - BUFFER)); newRange.end = Math.min( dataLength, this.getIndexForOffset(offset + viewportSize + BUFFER * 2) ); } } viewport.setRenderedRange(newRange); viewport.setRenderedContentOffset(this.getOffsetForIndex(newRange.start)); // Used for update of 2nd scroll that is listening if (this.calendarService.selectedScroll === this.SCROLL_TYPE) this.index$.next({ indx: firstVisibleIndex, scrollOffset: offset, cycleSize: this.FULL_SIZE, type: this.SCROLL_TYPE, }); } }