ng-cdk-table-virtual-scroll
Version:
Virtual scroll for for Angular Cdk Table
354 lines (346 loc) • 15.1 kB
JavaScript
import { __extends, __decorate } from 'tslib';
import { Injectable, NgZone, Input, ContentChild, Directive, forwardRef, NgModule } from '@angular/core';
import { distinctUntilChanged, filter, tap, takeWhile, takeUntil, switchMap, map } from 'rxjs/operators';
import { BehaviorSubject, Subscription, ReplaySubject, Subject } from 'rxjs';
import { VIRTUAL_SCROLL_STRATEGY } from '@angular/cdk/scrolling';
import { DataSource, CdkTable } from '@angular/cdk/table';
var TableVirtualScrollDataSource = /** @class */ (function (_super) {
__extends(TableVirtualScrollDataSource, _super);
function TableVirtualScrollDataSource(initialData) {
if (initialData === void 0) { initialData = []; }
var _this = _super.call(this) || this;
/** Stream emitting render data to the table (depends on ordered data changes). */
_this._renderData = new BehaviorSubject([]);
/**
* Subscription to the changes that should trigger an update to the table's rendered rows, such
* as filtering, sorting, pagination, or base data changes.
*/
_this._renderChangesSubscription = null;
_this._data = new BehaviorSubject(initialData);
_this.updateChangeSubscription();
return _this;
}
Object.defineProperty(TableVirtualScrollDataSource.prototype, "data", {
/** Array of data that should be rendered by the table, where each object represents one row. */
get: function () {
return this._data.value;
},
set: function (data) {
this._data.next(data);
},
enumerable: true,
configurable: true
});
TableVirtualScrollDataSource.prototype.updateChangeSubscription = function () {
var _this = this;
this.initStreams();
this._renderChangesSubscription = new Subscription();
this._renderChangesSubscription.add(this._data.subscribe(function (data) { return _this.dataToRender$.next(data); }));
this._renderChangesSubscription.add(this.dataOfRange$.subscribe(function (data) { return _this._renderData.next(data); }));
};
TableVirtualScrollDataSource.prototype.initStreams = function () {
if (!this.streamsReady) {
this.dataToRender$ = new ReplaySubject(1);
this.dataOfRange$ = new ReplaySubject(1);
this.streamsReady = true;
}
};
/**
* Used by the MatTable. Called when it connects to the data source.
* @private
*/
TableVirtualScrollDataSource.prototype.connect = function () {
if (!this._renderChangesSubscription) {
this.updateChangeSubscription();
}
return this._renderData;
};
/**
* Used by the MatTable. Called when it disconnects from the data source.
* @private
*/
TableVirtualScrollDataSource.prototype.disconnect = function () {
if (this._renderChangesSubscription) {
this._renderChangesSubscription.unsubscribe();
}
this._renderChangesSubscription = null;
};
return TableVirtualScrollDataSource;
}(DataSource));
var FixedSizeTableVirtualScrollStrategy = /** @class */ (function () {
function FixedSizeTableVirtualScrollStrategy() {
this.indexChange = new Subject();
this.stickyChange = new Subject();
this.renderedRangeStream = new BehaviorSubject({ start: 0, end: 0 });
this.scrolledIndexChange = this.indexChange.pipe(distinctUntilChanged());
this._dataLength = 0;
}
Object.defineProperty(FixedSizeTableVirtualScrollStrategy.prototype, "dataLength", {
get: function () {
return this._dataLength;
},
set: function (value) {
this._dataLength = value;
this.onDataLengthChanged();
},
enumerable: true,
configurable: true
});
FixedSizeTableVirtualScrollStrategy.prototype.attach = function (viewport) {
this.viewport = viewport;
this.viewport.renderedRangeStream.subscribe(this.renderedRangeStream);
this.onDataLengthChanged();
};
FixedSizeTableVirtualScrollStrategy.prototype.detach = function () {
this.indexChange.complete();
this.stickyChange.complete();
this.renderedRangeStream.complete();
};
FixedSizeTableVirtualScrollStrategy.prototype.onContentScrolled = function () {
this.updateContent();
};
FixedSizeTableVirtualScrollStrategy.prototype.onDataLengthChanged = function () {
if (this.viewport) {
this.viewport.setTotalContentSize(this.dataLength * this.rowHeight + this.headerHeight + this.footerHeight);
}
this.updateContent();
};
FixedSizeTableVirtualScrollStrategy.prototype.onContentRendered = function () {
// no-op
};
FixedSizeTableVirtualScrollStrategy.prototype.onRenderedOffsetChanged = function () {
// no-op
};
FixedSizeTableVirtualScrollStrategy.prototype.scrollToIndex = function (index, behavior) {
if (!this.viewport || !this.rowHeight) {
return;
}
this.viewport.scrollToOffset((index - 1) * this.rowHeight + this.headerHeight);
};
FixedSizeTableVirtualScrollStrategy.prototype.setConfig = function (configs) {
var rowHeight = configs.rowHeight, headerHeight = configs.headerHeight, footerHeight = configs.footerHeight, bufferMultiplier = configs.bufferMultiplier;
if (this.rowHeight === rowHeight
&& this.headerHeight === headerHeight
&& this.footerHeight === footerHeight
&& this.bufferMultiplier === bufferMultiplier) {
return;
}
this.rowHeight = rowHeight;
this.headerHeight = headerHeight;
this.footerHeight = footerHeight;
this.bufferMultiplier = bufferMultiplier;
this.onDataLengthChanged();
};
FixedSizeTableVirtualScrollStrategy.prototype.updateContent = function () {
if (!this.viewport || !this.rowHeight) {
return;
}
var scrollOffset = this.viewport.measureScrollOffset();
var amount = Math.ceil(this.viewport.getViewportSize() / this.rowHeight);
var offset = Math.max(scrollOffset - this.headerHeight, 0);
var buffer = Math.ceil(amount * this.bufferMultiplier);
var skip = Math.round(offset / this.rowHeight);
var index = Math.max(0, skip);
var start = Math.max(0, index - buffer);
var end = Math.min(this.dataLength, index + amount + buffer);
var renderedOffset = start * this.rowHeight;
this.viewport.setRenderedContentOffset(renderedOffset);
this.viewport.setRenderedRange({ start: start, end: end });
this.indexChange.next(index);
this.stickyChange.next(renderedOffset);
};
FixedSizeTableVirtualScrollStrategy = __decorate([
Injectable()
], FixedSizeTableVirtualScrollStrategy);
return FixedSizeTableVirtualScrollStrategy;
}());
function _tableVirtualScrollDirectiveStrategyFactory(tableDir) {
return tableDir.scrollStrategy;
}
var stickyHeaderSelector = '.cdk-header-row .cdk-table-sticky';
var stickyFooterSelector = '.cdk-footer-row .cdk-table-sticky';
var defaults = {
rowHeight: 48,
headerHeight: 56,
headerEnabled: true,
footerHeight: 48,
footerEnabled: false,
bufferMultiplier: 0.7,
};
var TableItemSizeDirective = /** @class */ (function () {
function TableItemSizeDirective(zone) {
this.zone = zone;
this.alive = true;
// tslint:disable-next-line:no-input-rename
this.rowHeight = defaults.rowHeight;
this.headerEnabled = defaults.headerEnabled;
this.headerHeight = defaults.headerHeight;
this.footerEnabled = defaults.footerEnabled;
this.footerHeight = defaults.footerHeight;
this.bufferMultiplier = defaults.bufferMultiplier;
this.scrollStrategy = new FixedSizeTableVirtualScrollStrategy();
this.dataSourceChanges = new Subject();
}
TableItemSizeDirective_1 = TableItemSizeDirective;
TableItemSizeDirective.prototype.ngOnDestroy = function () {
this.alive = false;
this.dataSourceChanges.complete();
};
TableItemSizeDirective.prototype.isAlive = function () {
var _this = this;
return function () { return _this.alive; };
};
TableItemSizeDirective.prototype.isStickyEnabled = function () {
return (!!this.scrollStrategy.viewport &&
this.table['_headerRowDefs']
.map(function (def) { return def.sticky; })
.reduce(function (prevState, state) { return prevState && state; }, true));
};
TableItemSizeDirective.prototype.ngAfterContentInit = function () {
var _this = this;
var switchDataSourceOrigin = this.table['_switchDataSource'];
this.table['_switchDataSource'] = function (dataSource) {
switchDataSourceOrigin.call(_this.table, dataSource);
_this.connectDataSource(dataSource);
};
this.connectDataSource(this.table.dataSource);
this.scrollStrategy.stickyChange
.pipe(filter(function () { return _this.isStickyEnabled(); }), tap(function () {
if (!_this.stickyPositions) {
_this.initStickyPositions();
}
}), takeWhile(this.isAlive()))
.subscribe(function (stickyOffset) {
_this.setSticky(stickyOffset);
});
};
TableItemSizeDirective.prototype.connectDataSource = function (dataSource) {
var _this = this;
this.dataSourceChanges.next();
if (dataSource instanceof TableVirtualScrollDataSource) {
dataSource.dataToRender$
.pipe(distinctUntilChanged(), takeUntil(this.dataSourceChanges), takeWhile(this.isAlive()), tap(function (data) { return (_this.scrollStrategy.dataLength = data.length); }), switchMap(function (data) {
return _this.scrollStrategy.renderedRangeStream.pipe(map(function (_a) {
var start = _a.start, end = _a.end;
return typeof start !== 'number' || typeof end !== 'number'
? data
: data.slice(start, end);
}));
}))
.subscribe(function (data) {
_this.zone.run(function () {
dataSource.dataOfRange$.next(data);
});
});
}
else {
throw new Error('[tvsItemSize] requires TableVirtualScrollDataSource be set as [dataSource] of [cdk-table]');
}
};
TableItemSizeDirective.prototype.ngOnChanges = function () {
var config = {
rowHeight: +this.rowHeight || defaults.rowHeight,
headerHeight: this.headerEnabled
? +this.headerHeight || defaults.headerHeight
: 0,
footerHeight: this.footerEnabled
? +this.footerHeight || defaults.footerHeight
: 0,
bufferMultiplier: +this.bufferMultiplier || defaults.bufferMultiplier,
};
this.scrollStrategy.setConfig(config);
};
TableItemSizeDirective.prototype.setSticky = function (offset) {
var _this = this;
this.scrollStrategy.viewport.elementRef.nativeElement
.querySelectorAll(stickyHeaderSelector)
.forEach(function (el) {
var parent = el.parentElement;
var baseOffset = 0;
if (_this.stickyPositions.has(parent)) {
baseOffset = _this.stickyPositions.get(parent);
}
el.style.top = baseOffset - offset + "px";
});
this.scrollStrategy.viewport.elementRef.nativeElement
.querySelectorAll(stickyFooterSelector)
.forEach(function (el) {
var parent = el.parentElement;
var baseOffset = 0;
if (_this.stickyPositions.has(parent)) {
baseOffset = _this.stickyPositions.get(parent);
}
el.style.bottom = -baseOffset + offset + "px";
});
};
TableItemSizeDirective.prototype.initStickyPositions = function () {
var _this = this;
this.stickyPositions = new Map();
this.scrollStrategy.viewport.elementRef.nativeElement
.querySelectorAll(stickyHeaderSelector)
.forEach(function (el) {
var parent = el.parentElement;
if (!_this.stickyPositions.has(parent)) {
_this.stickyPositions.set(parent, parent.offsetTop);
}
});
};
var TableItemSizeDirective_1;
TableItemSizeDirective.ctorParameters = function () { return [
{ type: NgZone }
]; };
__decorate([
Input('tvsItemSize')
], TableItemSizeDirective.prototype, "rowHeight", void 0);
__decorate([
Input()
], TableItemSizeDirective.prototype, "headerEnabled", void 0);
__decorate([
Input()
], TableItemSizeDirective.prototype, "headerHeight", void 0);
__decorate([
Input()
], TableItemSizeDirective.prototype, "footerEnabled", void 0);
__decorate([
Input()
], TableItemSizeDirective.prototype, "footerHeight", void 0);
__decorate([
Input()
], TableItemSizeDirective.prototype, "bufferMultiplier", void 0);
__decorate([
ContentChild(CdkTable, { static: false })
], TableItemSizeDirective.prototype, "table", void 0);
TableItemSizeDirective = TableItemSizeDirective_1 = __decorate([
Directive({
selector: 'cdk-virtual-scroll-viewport[tvsItemSize]',
providers: [
{
provide: VIRTUAL_SCROLL_STRATEGY,
useFactory: _tableVirtualScrollDirectiveStrategyFactory,
deps: [forwardRef(function () { return TableItemSizeDirective_1; })],
},
],
})
], TableItemSizeDirective);
return TableItemSizeDirective;
}());
var TableVirtualScrollModule = /** @class */ (function () {
function TableVirtualScrollModule() {
}
TableVirtualScrollModule = __decorate([
NgModule({
declarations: [TableItemSizeDirective],
exports: [TableItemSizeDirective],
imports: [],
})
], TableVirtualScrollModule);
return TableVirtualScrollModule;
}());
/*
* Public API Surface of ng-fixed-size-table-virtual-scroll
*/
/**
* Generated bundle index. Do not edit.
*/
export { FixedSizeTableVirtualScrollStrategy, TableItemSizeDirective, TableVirtualScrollDataSource, TableVirtualScrollModule, _tableVirtualScrollDirectiveStrategyFactory };
//# sourceMappingURL=ng-cdk-table-virtual-scroll.js.map