Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(template): add DataSource support for rx-virtual-for directive #1764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

friendlyAce
Copy link
Contributor

@friendlyAce friendlyAce commented Aug 31, 2024

Proposal
Link to feature request: #1765

Currently, the rx virtual for directive supports rendering from signal, observable and static iterables. This proposal aims to extends the functionality by adding support for the DataSource interface that is supported in the CdkVirtualForOf of the Angular CDK. The rx virtual for directive could directly connect to and disconnect from the given DataSource when needed.

This PR introduces support for DataSource in rx-virtual-for, aligning with Angular CDK's implementation while leveraging rx-angular's powerful reactive approach.

When users want to use their existing data source implementations they will need to implement the CollectionViewer interface at the component that uses rx virtual for and link the viewRange: ListRange output of that virtual for directive to their DataSource implementation for connecting and passing the returned Observable to rxVirtualFor and handle disconnecting from that data source when needed.

Key features:

  • Implement DataSource interface compatible with rx-virtual-for
  • Add support for DataSource input in the rxVirtualForOf directive
  • Enhance the rxVirtualForOf to handle the DataSource input case, the connection to and disconnection from the data source

Benefits:

  • Improved compatibility with te existing Angular CDK patterns
  • Enhanced flexibility for data handling in virtual scrolling scenarios
  • Better integration with complex data fetching and manipulation use cases (lazy on demand requesting data, filtering, etc.)

Implementation details:

  • Modified rxVirtualForOf input to accept DataSource
  • Add use-case to check whether the input is a data source
  • Updated internal logic to connect to the DataSource using the current ListRange and disconnect from the DataSource when destroyed

Documentation:

  • Needs to be added if the feature proposal is desired (apps\docs\docs\template\api\virtual-scrolling.mdx)

In addition to the implementation details mentioned above, I have declared the necessary types such as DataSource, CollectionViewer, and the isDataSource method within the rx-angular template package itself.

This approach eliminates the need for a (peer) dependency on Angular CDK, ensuring that rx-virtual-for remains self-contained and independent, similar how it was done with the ListRange interface.

Feedback, concerns, improvements are more than welcome

BR,
Vincent

@github-actions github-actions bot added </> Template @rx-angular/template related 🔬 Experimental Experimental: Feature, docs, demos labels Aug 31, 2024
Copy link

nx-cloud bot commented Aug 31, 2024

View your CI Pipeline Execution ↗ for commit 09270f9.

Command Status Duration Result
nx affected -t lint build test component-test e... ⏳ In Progress ... View ↗
nx build docs ✅ Succeeded 32s View ↗
nx-cloud record -- npx nx format:check ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2025-05-24 22:44:42 UTC

@hoebbelsB
Copy link
Member

hey @friendlyAce thanks a lot for your contribution! Will make a review over the weekend!

@@ -251,6 +255,21 @@ export class RxVirtualFor<T, U extends NgIterable<T> = NgIterable<T>>
this.observables$.next(
toObservable(potentialSignalOrObservable, { injector: this.injector }),
);
} else if (this.isDataSource(potentialSignalOrObservable)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't it be potentialSignalOrObservableOrDataSource now? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll rename it from potentialSignalOrObservable to potentialSignalOrObservableOrDataSource

@@ -34,6 +34,17 @@ export interface ListRange {
end: number;
}

export abstract class DataSource<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you know if it's possible to maybe have an import type { DataSource } from '@angular/cdk' without having it to mention as peerDependency?

That would be the absolute best case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoebbelsB Hmm im not fully sure on that, i tried to have a bit of testing on that but i kept running into a bunch of possibly unrelated issues and stopped checking.

My idea was to use Typescripts "type only imports" e. g. import type { DataSource } from '@angular/cdk/collections (doc)
As when compiling they should just be dropped out of the dist output files
For that i reverted the DataSource and CollectionViewer declarations which i added in

diff --git a/libs/template/experimental/virtual-scrolling/src/index.ts b/libs/template/experimental/virtual-scrolling/src/index.ts
index 86b34086..8c50f1ed 100644
--- a/libs/template/experimental/virtual-scrolling/src/index.ts
+++ b/libs/template/experimental/virtual-scrolling/src/index.ts
@@ -1,6 +1,4 @@
 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 c6ed5c35..aacb1d3e 100644
--- a/libs/template/experimental/virtual-scrolling/src/lib/model.ts
+++ b/libs/template/experimental/virtual-scrolling/src/lib/model.ts
@@ -34,17 +34,6 @@ export interface ListRange {
   end: number;
 }
 
-export abstract class DataSource<T> {
-  abstract connect(
-    collectionViewer: CollectionViewer,
-  ): Observable<NgIterable<T>>;
-  abstract disconnect(collectionViewer: CollectionViewer): void;
-}
-
-export interface CollectionViewer {
-  viewChange: Observable<ListRange>;
-}
-
 /**
  * @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 174faf3e..14ee2512 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
@@ -56,8 +56,6 @@ import {
   tap,
 } from 'rxjs/operators';
 import {
-  CollectionViewer,
-  DataSource,
   ListRange,
   RxVirtualForViewContext,
   RxVirtualScrollStrategy,
@@ -67,6 +65,10 @@ import {
   createVirtualListTemplateManager,
   RxVirtualListTemplateManager,
 } from './virtual-list-template-manager';
+import type {
+  DataSource,
+  CollectionViewer
+} from '@angular/cdk/collections';
 import {
   DEFAULT_TEMPLATE_CACHE_SIZE,
   RX_VIRTUAL_SCROLL_DEFAULT_OPTIONS,

And instead i used the type only imports directly in virtual-for.directive.ts, e. g.

import type {
  DataSource,
  CollectionViewer
} from '@angular/cdk/collections';

Therefore, at least for the library build, the @angular/cdk package needs to be resolvable, which it is, as its already part of your package.json dependencies here.

After these changes i ran the build for the template library via npx nx run template:build:production, which gave me the according dist output dist\libs\template.

Taking a short look at the compiled .mjs file, indeed the import from @angular/cdk is NOT mentioned (as its a type import)
dist\libs\template\fesm2022\template-experimental-virtual-scrolling.mjs

But when having a look at the compiled type declaration file:
dist\libs\template\experimental\virtual-scrolling\lib\virtual-for.directive.d.ts
It still contains the @angular/cdk import...
import type { DataSource } from '@angular/cdk/collections';
Which could cause an issue for library consumers.

So i wanted to check this and therefore created a new nx angular workspace via
npx create-nx-workspace demo-workspace --preset=angular
And installed the local dist template library via
npm i ../rx-angular/dist/libs/template
package.json => "@rx-angular/template": "file:../rx-angular/dist/libs/template"
I also ensured that the @angular/cdk package is not installed in this new demo-workspace project

Then i creating a small dummy component (similar to this example), that uses the virtual for directive, the viewport etc. to see if the serve and build of that demo application would work or if i would run into some errors related to the missing angular/cdk peer dependency.

But unfortunately my serve had a bunch of unrelated errors in regards of type conflicts of rxjs

Type 'import("DEMO-WORKSPACE/node_modules/rxjs/dist/types/internal/Observable").Observable<any> | undefined' is not assignable to type 'import("rx-angular/node_modules/rxjs/dist/types/internal/Observable").Observable<any> | undefined'.
Types of property 'operator' are incompatible.

Which is a classic issue when using local packages, as the workspaces have their own node_modules directories with their own rxjs installations, which TS sees as incompatible
So instead i used npm links

npm uninstall @rx-angular/cdk @rx-angular/template
cd rx-angular/dist/libs/template
npm link
cd demo-workspace
npm link @rx-angular/template

$ npm ls -g --link=true
C:\Users...\AppData\Roaming\npm
└── @rx-angular/[email protected] -> rx-angular\dist\libs\template

alternatively, removing the rx-angular/node_modules directory also works.

But nevertheless when serving i keep getting this error.
image

So either something is broken with the local build of the template lib, or something else. After trying a bunch of stuff, i couldn't get this fixed and therefore couldn't properly check if this approach would work for library consumers.

But i would probably still expect, something like "Cannot find module '@angular/cdk/collections' or its corresponding type declarations" when somebody uses the rx-angular/template library, without them having the angular cdk package also installed

Comment on lines 270 to 272
this._destroy$.pipe(take(1)).subscribe(() => {
potentialSignalOrObservable.disconnect(collectionViewer);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not a big fan of that. We should do the disconnect in ngOnDestroy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the connectedDataSource and collectionViewer properties for being able to reference the objects
I also added disconnectDataSource() now, which does the clean up for the data source instead of doing it with the subscription to _destroy$.

disconnectDataSource will now be invoked in the ngOnDestroy lifecycle hook
I also added that on new input changes to rxVirtualForOf, the previous data source gets disconnected, in case the user does not destroy the component, but re-uses it and just updates the input with a new data source implementation instead)

Comment on lines 293 to 294
value !== null &&
value !== undefined &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't this be shortened with value != null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it can, ill fix it

- Renamed potentialSignalOrObservable to potentialSignalOrObservableOrDataSource
- Move DataSource disconnect from subscription to ngOnDestroy lifecycle
- Add disconnectDataSource() method for centralized cleanup logic
- Ensure disconnect and cleanup of previous data source on data source input changes to prevent memory leaks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔬 Experimental Experimental: Feature, docs, demos </> Template @rx-angular/template related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants