components
components copied to clipboard
bug(cdk) Replacing datasource does not refresh/display items in cdk-virtual-scrollport
Overview
The first time you assign a datasource for iteration within the cdk-virtual-scrollport, all works as expected, as per the documentation example of displaying the first page of items. See "Virtual scroll with a custom data source" on https://material.angular.io/cdk/scrolling/examples
The bug however is if you subsequently try to assign a new instance of a datasource object to the same scroller. You will find that the scrollport will not request/display the first page of items from that new datasource until the user scrolls the visible area.
Motivation/Use Case In the real-world our virtual data can require some sort of user driven filtering, like a search screen. Which will require a complete replacement of all the underlying/cached data in the scroller with fresh results. This is ideally achieved by replacing the datasource when the user triggers the search.
Expected Behavior Scroller should reset itself automatically when given a new datasource and request the first page of rows in exactly the same way it does when it was assigned the first datasource.
Actual Behavior CollectionViewer does not fire an event to the subscription in connect() until it has a range that differs.
Minimal repro: See https://stackblitz.com/edit/angular-fs9xei-rgkahy?file=src%2Fapp%2Fcdk-virtual-scroll-data-source-example.ts
Run the sample, initial list items will appear. Click on the "Switch datasource button", and you will see the items in the list stuck with saying "loading..." indefinitely. You have to scroll before the datasource actually gets asked for data to be able to be displayed.
Environment Angular: 11 CDK/Material: 11 Browser(s): n/a Operating System (e.g. Windows, macOS, Ubuntu): n/a
No comments on this? Can't believe that no-one else is having the same problem. I have been spelunking through some of the source code, there does appear at some point to have been an attempt to handle datasource changing but clearly it either never worked or has been broken since then.
I have just now accidentally stumbled on a workaround of sorts. I made the cdk-virtual-scroll-viewport element conditionally displayed using an *ngIf based on some searching status variable, and once I have the results the datasource is changed along with that variable and the virtual scroll viewport will be flipped back to visible. That hiding/showing seems to be sufficient for it to reset all its internal state on it's scroll position such that the replacement data is rendered correctly.
Whether others will approve the "flicker" of the list disappearing/reappearing is yet to be seen of course, but at least now the data will render in the scroller without requiring manual scrolling.
I confirm that I have the same problem. I'm going to try different solutions. Right now I see that _fetchedPages can be reset to new Set
Update: So I managed to reset the whole cdk-virtual-scroll-viewport by using dynamic component loader: https://angular.io/guide/dynamic-component-loader My update function for index with virtual scroll now looks like this:
ngOnInit(): void {
this.infoService.shelf.asObservable().subscribe(
shelf => {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(IndexComponent);
const viewContainerRef = this.indexHost.viewContainerRef;
viewContainerRef.clear();
if (this.infoService.getDictionaryIds().length>0) {
const componentRef = viewContainerRef.createComponent<IndexComponent>(componentFactory);
//componentRef.instance.data = adItem.data;
}
});
}
Update #2: I tested more and finally gave up. I couldn't reset it so I went on to find another solution.
As far as I can tell, the problem has nothing to do with itemSize. I created a repro based on the custom data source example, which uses a fixed itemSize:
https://stackblitz.com/edit/angular-jqnvlc?file=src%2Fapp%2Fcdk-virtual-scroll-data-source-example.ts
I assume the OP did something similar, but that stackblitz is no longer accessible.
I have verified that using cdkVirtualForTrackBy does not resolve this issue. The only workaround I've found so far is to recreate the viewport when the data source changes:
https://stackblitz.com/edit/angular-jqnvlc-9ndymf?file=src%2Fapp%2Fcdk-virtual-scroll-data-source-example.html
Found a better workaround. Expose a method from the data source that handles range changes (i.e., that handles the subscription to viewChange). Manually call that method with getRenderedRange from the viewport prior to assigning to CdkVirtualForOf.
https://stackblitz.com/edit/angular-jqnvlc-ze1rfu?file=src%2Fapp%2Fcdk-virtual-scroll-data-source-example.ts
this issue could be happening if there is an ngIf wrapped on the cdkVirtualFor.
In 2024 this issue is still here.. (ANGULAR 17 / IONIC) I followed kiwidude68's example and i inserted an if condition on the cdk-virtual-scroll-viewport that makes it disappear while retrieving the dataset and makes it visible again when the data arrives. All it took was a variable that is set to true at the beginning of the data recovery operation and once the response arrives it resets to false. This workaround still solves the problem today, but it is only a workaround.
this issue could be happening if there is an ngIf wrapped on the cdkVirtualFor.
Thank you, @Predhin
this was exactly what was causing the issue for me. I wanted to show a "no data found" block on the page if the data array was empty, and kept running into this issue. I printed the {{ data.length }} and {{ data[0] | json }}, and everything printed out right, but the scroll list wouldn't render.
I removed the @if from around the cdkVirtualFor block and things were back to normal.
So, now my block looks like this:
<cdk-virtual-scroll-viewport ....>
<div *cdkVirtualFor="let contact of (data$ | async); trackBy: trackById">
...
...
</div>
</cdk-virtual-scroll-viewport>
@if ((data$ | async)?.length === 0) {
<div>No data found</div>
}