diff --git a/libs/template/experimental/virtual-scrolling/src/index.ts b/libs/template/experimental/virtual-scrolling/src/index.ts index 8c50f1ed3..86b340861 100644 --- a/libs/template/experimental/virtual-scrolling/src/index.ts +++ b/libs/template/experimental/virtual-scrolling/src/index.ts @@ -1,4 +1,6 @@ export { + CollectionViewer, + DataSource, ListRange, RxVirtualForViewContext, RxVirtualScrollElement, diff --git a/libs/template/experimental/virtual-scrolling/src/lib/model.ts b/libs/template/experimental/virtual-scrolling/src/lib/model.ts index aacb1d3e8..c6ed5c35f 100644 --- a/libs/template/experimental/virtual-scrolling/src/lib/model.ts +++ b/libs/template/experimental/virtual-scrolling/src/lib/model.ts @@ -34,6 +34,17 @@ export interface ListRange { end: number; } +export abstract class DataSource { + abstract connect( + collectionViewer: CollectionViewer, + ): Observable>; + abstract disconnect(collectionViewer: CollectionViewer): void; +} + +export interface CollectionViewer { + viewChange: Observable; +} + /** * @Directive RxVirtualScrollStrategy * diff --git a/libs/template/experimental/virtual-scrolling/src/lib/virtual-for.directive.ts b/libs/template/experimental/virtual-scrolling/src/lib/virtual-for.directive.ts index 4b39c5d0c..174faf3ee 100644 --- a/libs/template/experimental/virtual-scrolling/src/lib/virtual-for.directive.ts +++ b/libs/template/experimental/virtual-scrolling/src/lib/virtual-for.directive.ts @@ -34,6 +34,7 @@ import { Promise } from '@rx-angular/cdk/zone-less/browser'; import { combineLatest, concat, + ConnectableObservable, isObservable, MonoTypeOperatorFunction, NEVER, @@ -55,6 +56,8 @@ import { tap, } from 'rxjs/operators'; import { + CollectionViewer, + DataSource, ListRange, RxVirtualForViewContext, RxVirtualScrollStrategy, @@ -213,6 +216,11 @@ export class RxVirtualFor = NgIterable> optional: true, }); + /** @internal */ + private connectedDataSource?: DataSource; + /** @internal */ + private collectionViewer?: CollectionViewer; + /** @internal */ private _differ?: IterableDiffer; @@ -234,33 +242,69 @@ export class RxVirtualFor = NgIterable> * [hero]="hero"> * * - * @param potentialSignalOrObservable + * @param potentialSignalOrObservableOrDataSource */ @Input() set rxVirtualForOf( - potentialSignalOrObservable: + potentialSignalOrObservableOrDataSource: | Observable<(U & NgIterable) | undefined | null> | Signal<(U & NgIterable) | undefined | null> + | DataSource | (U & NgIterable) | null | undefined, ) { - if (isSignal(potentialSignalOrObservable)) { + if (isSignal(potentialSignalOrObservableOrDataSource)) { + this.staticValue = undefined; + this.renderStatic = false; + this.observables$.next( + toObservable(potentialSignalOrObservableOrDataSource, { + injector: this.injector, + }), + ); + } else if (this.isDataSource(potentialSignalOrObservableOrDataSource)) { + this.disconnectDataSource(); + this.staticValue = undefined; this.renderStatic = false; + + const collectionViewer: CollectionViewer = { + viewChange: this.scrollStrategy.renderedRange$, + }; + + this.collectionViewer = collectionViewer; + this.connectedDataSource = potentialSignalOrObservableOrDataSource; + this.observables$.next( - toObservable(potentialSignalOrObservable, { injector: this.injector }), + potentialSignalOrObservableOrDataSource.connect(collectionViewer), ); - } else if (!isObservable(potentialSignalOrObservable)) { - this.staticValue = potentialSignalOrObservable; + } else if (!isObservable(potentialSignalOrObservableOrDataSource)) { + this.staticValue = potentialSignalOrObservableOrDataSource; this.renderStatic = true; } else { this.staticValue = undefined; this.renderStatic = false; - this.observables$.next(potentialSignalOrObservable); + this.observables$.next(potentialSignalOrObservableOrDataSource); } } + /** @internal */ + private isDataSource( + value: + | (U & NgIterable) + | Observable> + | DataSource + | null + | undefined, + ): value is DataSource { + return ( + value != null && + 'connect' in value && + typeof value.connect === 'function' && + !(value instanceof ConnectableObservable) + ); + } + /** * @internal * A reference to the template that is created for each item in the iterable. @@ -640,8 +684,18 @@ export class RxVirtualFor = NgIterable> } } + /** @internal */ + private disconnectDataSource(): void { + if (this.connectedDataSource && this.collectionViewer) { + this.connectedDataSource.disconnect(this.collectionViewer); + this.connectedDataSource = undefined; + this.collectionViewer = undefined; + } + } + /** @internal */ ngOnDestroy() { + this.disconnectDataSource(); this._destroy$.next(); this.templateManager.detach(); }