Angular Material VirtualScrollViewPort - load data async
I find different samples for the VirtualScrollViewPort but with all I have the problem how to use them in real life. The samples load the whole data at once from the server - but since they are too large, I wanna load them individually. My main requirements are:
- Search mask - user enters some data
- Display progress bar...
- Query a Search on the server
- If no result is found ==> display a message
- If something is found ==> display the first n (=10) items
- After the user scrolls down and only e.g. 5 items are left ==> load additionally 10 items
- continue with 5.
- if only e.g. 3 are left ==> end the scrolling
I tried already the approach from Specifying data
section - but there I fail how to recognize that no data is loaded AND I fail to initiate the view - especially when the user resets the content.
I tried as well with
<cdk-virtual-scroll-viewport itemSize="itemHeight" (scrolledIndexChange)="nextBatch($event,(resultList[resultList.length-1].total) )"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
which works for the first requirements but finally I fail with scrolledIndexChange
is fired only on the very first item on the list. I have no clue how to track if user already is displaying already item 6 (which would load additional data). On the API page I do not see any @Output()
beside the scrolledIndexChange
.
Any hint how to track the events properly?
UPDATE
First problem I figured out was the incorrect syntax of setting the height, i.e. [itemSize]="itemHeight"
is the appropriate syntax otherwise it remains always to zero ==> all elements are rendered!

add a comment |
I find different samples for the VirtualScrollViewPort but with all I have the problem how to use them in real life. The samples load the whole data at once from the server - but since they are too large, I wanna load them individually. My main requirements are:
- Search mask - user enters some data
- Display progress bar...
- Query a Search on the server
- If no result is found ==> display a message
- If something is found ==> display the first n (=10) items
- After the user scrolls down and only e.g. 5 items are left ==> load additionally 10 items
- continue with 5.
- if only e.g. 3 are left ==> end the scrolling
I tried already the approach from Specifying data
section - but there I fail how to recognize that no data is loaded AND I fail to initiate the view - especially when the user resets the content.
I tried as well with
<cdk-virtual-scroll-viewport itemSize="itemHeight" (scrolledIndexChange)="nextBatch($event,(resultList[resultList.length-1].total) )"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
which works for the first requirements but finally I fail with scrolledIndexChange
is fired only on the very first item on the list. I have no clue how to track if user already is displaying already item 6 (which would load additional data). On the API page I do not see any @Output()
beside the scrolledIndexChange
.
Any hint how to track the events properly?
UPDATE
First problem I figured out was the incorrect syntax of setting the height, i.e. [itemSize]="itemHeight"
is the appropriate syntax otherwise it remains always to zero ==> all elements are rendered!

add a comment |
I find different samples for the VirtualScrollViewPort but with all I have the problem how to use them in real life. The samples load the whole data at once from the server - but since they are too large, I wanna load them individually. My main requirements are:
- Search mask - user enters some data
- Display progress bar...
- Query a Search on the server
- If no result is found ==> display a message
- If something is found ==> display the first n (=10) items
- After the user scrolls down and only e.g. 5 items are left ==> load additionally 10 items
- continue with 5.
- if only e.g. 3 are left ==> end the scrolling
I tried already the approach from Specifying data
section - but there I fail how to recognize that no data is loaded AND I fail to initiate the view - especially when the user resets the content.
I tried as well with
<cdk-virtual-scroll-viewport itemSize="itemHeight" (scrolledIndexChange)="nextBatch($event,(resultList[resultList.length-1].total) )"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
which works for the first requirements but finally I fail with scrolledIndexChange
is fired only on the very first item on the list. I have no clue how to track if user already is displaying already item 6 (which would load additional data). On the API page I do not see any @Output()
beside the scrolledIndexChange
.
Any hint how to track the events properly?
UPDATE
First problem I figured out was the incorrect syntax of setting the height, i.e. [itemSize]="itemHeight"
is the appropriate syntax otherwise it remains always to zero ==> all elements are rendered!

I find different samples for the VirtualScrollViewPort but with all I have the problem how to use them in real life. The samples load the whole data at once from the server - but since they are too large, I wanna load them individually. My main requirements are:
- Search mask - user enters some data
- Display progress bar...
- Query a Search on the server
- If no result is found ==> display a message
- If something is found ==> display the first n (=10) items
- After the user scrolls down and only e.g. 5 items are left ==> load additionally 10 items
- continue with 5.
- if only e.g. 3 are left ==> end the scrolling
I tried already the approach from Specifying data
section - but there I fail how to recognize that no data is loaded AND I fail to initiate the view - especially when the user resets the content.
I tried as well with
<cdk-virtual-scroll-viewport itemSize="itemHeight" (scrolledIndexChange)="nextBatch($event,(resultList[resultList.length-1].total) )"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
which works for the first requirements but finally I fail with scrolledIndexChange
is fired only on the very first item on the list. I have no clue how to track if user already is displaying already item 6 (which would load additional data). On the API page I do not see any @Output()
beside the scrolledIndexChange
.
Any hint how to track the events properly?
UPDATE
First problem I figured out was the incorrect syntax of setting the height, i.e. [itemSize]="itemHeight"
is the appropriate syntax otherwise it remains always to zero ==> all elements are rendered!


edited Jan 3 at 10:37
LeO
asked Jan 2 at 16:28
LeOLeO
688932
688932
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
After some work my final solution looks like:
<ng-container *ngIf="lstSearchResults|async as resultList; else searching">
<cdk-virtual-scroll-viewport [itemSize]="itemHeight" (scrolledIndexChange)="nextBatch()"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
where it is noteworthy that my list is an async list, named lstSearchResults
and in the the ts
code I have:
// for proper height and caching... (in pixels)
itemHeight = 174;
search(searchConfig:SearchOptions):void {
....
this.lstSearchResults = new BehaviorSubject<SearchResult>(null);
// call the REST service
this.searchService.doSearch(searchConfig).subscribe(foundEntry => {
if (!this.resultList) {
// first list - nothing found up till now
this.resultList = foundEntry;
} else {
if (!this.resultList[this.resultList.length - 1]) {
//remove the marker (which was added below/previously)
this.resultList.pop();
}
foundEntry.map(item => this.resultList.push(item));
}
if (this.resultList[0] && this.resultList[0].total > this.resultList.length + 1) {
//some more elements could be fetched from the server ==> add a dummy entry for rendering
this.resultList.push(undefined);
}
// notify the search list to be updated
this.lstSearchResults.next(this.resultList);
and for the scrolling I have the following code:
nextBatch(): void {
if (this.theEnd) {
return;
}
if (this.resultList[0]) {
// something was found
if (this.viewport.getRenderedRange().end === this.viewport.getDataLength()) {
// since we scrolled to the very end of the rendered display
// ==> check if further search is required (and do so...)
const searchTotal = this.resultList[0].total;
this.mySearchConfig.posOffset += this.mySearchConfig.noOfElements;
// some basic check if the total counter exceeds the current offset
// i.e. no further search required
if (this.mySearchConfig.posOffset <= searchTotal) {
this.search(this.mySearchConfig, true);
} else {
this.theEnd = true;
}
}
} else {
// nothing found ==> mark the end
this.theEnd = true;
}
}
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54009818%2fangular-material-virtualscrollviewport-load-data-async%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
After some work my final solution looks like:
<ng-container *ngIf="lstSearchResults|async as resultList; else searching">
<cdk-virtual-scroll-viewport [itemSize]="itemHeight" (scrolledIndexChange)="nextBatch()"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
where it is noteworthy that my list is an async list, named lstSearchResults
and in the the ts
code I have:
// for proper height and caching... (in pixels)
itemHeight = 174;
search(searchConfig:SearchOptions):void {
....
this.lstSearchResults = new BehaviorSubject<SearchResult>(null);
// call the REST service
this.searchService.doSearch(searchConfig).subscribe(foundEntry => {
if (!this.resultList) {
// first list - nothing found up till now
this.resultList = foundEntry;
} else {
if (!this.resultList[this.resultList.length - 1]) {
//remove the marker (which was added below/previously)
this.resultList.pop();
}
foundEntry.map(item => this.resultList.push(item));
}
if (this.resultList[0] && this.resultList[0].total > this.resultList.length + 1) {
//some more elements could be fetched from the server ==> add a dummy entry for rendering
this.resultList.push(undefined);
}
// notify the search list to be updated
this.lstSearchResults.next(this.resultList);
and for the scrolling I have the following code:
nextBatch(): void {
if (this.theEnd) {
return;
}
if (this.resultList[0]) {
// something was found
if (this.viewport.getRenderedRange().end === this.viewport.getDataLength()) {
// since we scrolled to the very end of the rendered display
// ==> check if further search is required (and do so...)
const searchTotal = this.resultList[0].total;
this.mySearchConfig.posOffset += this.mySearchConfig.noOfElements;
// some basic check if the total counter exceeds the current offset
// i.e. no further search required
if (this.mySearchConfig.posOffset <= searchTotal) {
this.search(this.mySearchConfig, true);
} else {
this.theEnd = true;
}
}
} else {
// nothing found ==> mark the end
this.theEnd = true;
}
}
add a comment |
After some work my final solution looks like:
<ng-container *ngIf="lstSearchResults|async as resultList; else searching">
<cdk-virtual-scroll-viewport [itemSize]="itemHeight" (scrolledIndexChange)="nextBatch()"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
where it is noteworthy that my list is an async list, named lstSearchResults
and in the the ts
code I have:
// for proper height and caching... (in pixels)
itemHeight = 174;
search(searchConfig:SearchOptions):void {
....
this.lstSearchResults = new BehaviorSubject<SearchResult>(null);
// call the REST service
this.searchService.doSearch(searchConfig).subscribe(foundEntry => {
if (!this.resultList) {
// first list - nothing found up till now
this.resultList = foundEntry;
} else {
if (!this.resultList[this.resultList.length - 1]) {
//remove the marker (which was added below/previously)
this.resultList.pop();
}
foundEntry.map(item => this.resultList.push(item));
}
if (this.resultList[0] && this.resultList[0].total > this.resultList.length + 1) {
//some more elements could be fetched from the server ==> add a dummy entry for rendering
this.resultList.push(undefined);
}
// notify the search list to be updated
this.lstSearchResults.next(this.resultList);
and for the scrolling I have the following code:
nextBatch(): void {
if (this.theEnd) {
return;
}
if (this.resultList[0]) {
// something was found
if (this.viewport.getRenderedRange().end === this.viewport.getDataLength()) {
// since we scrolled to the very end of the rendered display
// ==> check if further search is required (and do so...)
const searchTotal = this.resultList[0].total;
this.mySearchConfig.posOffset += this.mySearchConfig.noOfElements;
// some basic check if the total counter exceeds the current offset
// i.e. no further search required
if (this.mySearchConfig.posOffset <= searchTotal) {
this.search(this.mySearchConfig, true);
} else {
this.theEnd = true;
}
}
} else {
// nothing found ==> mark the end
this.theEnd = true;
}
}
add a comment |
After some work my final solution looks like:
<ng-container *ngIf="lstSearchResults|async as resultList; else searching">
<cdk-virtual-scroll-viewport [itemSize]="itemHeight" (scrolledIndexChange)="nextBatch()"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
where it is noteworthy that my list is an async list, named lstSearchResults
and in the the ts
code I have:
// for proper height and caching... (in pixels)
itemHeight = 174;
search(searchConfig:SearchOptions):void {
....
this.lstSearchResults = new BehaviorSubject<SearchResult>(null);
// call the REST service
this.searchService.doSearch(searchConfig).subscribe(foundEntry => {
if (!this.resultList) {
// first list - nothing found up till now
this.resultList = foundEntry;
} else {
if (!this.resultList[this.resultList.length - 1]) {
//remove the marker (which was added below/previously)
this.resultList.pop();
}
foundEntry.map(item => this.resultList.push(item));
}
if (this.resultList[0] && this.resultList[0].total > this.resultList.length + 1) {
//some more elements could be fetched from the server ==> add a dummy entry for rendering
this.resultList.push(undefined);
}
// notify the search list to be updated
this.lstSearchResults.next(this.resultList);
and for the scrolling I have the following code:
nextBatch(): void {
if (this.theEnd) {
return;
}
if (this.resultList[0]) {
// something was found
if (this.viewport.getRenderedRange().end === this.viewport.getDataLength()) {
// since we scrolled to the very end of the rendered display
// ==> check if further search is required (and do so...)
const searchTotal = this.resultList[0].total;
this.mySearchConfig.posOffset += this.mySearchConfig.noOfElements;
// some basic check if the total counter exceeds the current offset
// i.e. no further search required
if (this.mySearchConfig.posOffset <= searchTotal) {
this.search(this.mySearchConfig, true);
} else {
this.theEnd = true;
}
}
} else {
// nothing found ==> mark the end
this.theEnd = true;
}
}
After some work my final solution looks like:
<ng-container *ngIf="lstSearchResults|async as resultList; else searching">
<cdk-virtual-scroll-viewport [itemSize]="itemHeight" (scrolledIndexChange)="nextBatch()"
class="scroll-container">
<div *cdkVirtualFor="let search of resultList" class="card-item" >
where it is noteworthy that my list is an async list, named lstSearchResults
and in the the ts
code I have:
// for proper height and caching... (in pixels)
itemHeight = 174;
search(searchConfig:SearchOptions):void {
....
this.lstSearchResults = new BehaviorSubject<SearchResult>(null);
// call the REST service
this.searchService.doSearch(searchConfig).subscribe(foundEntry => {
if (!this.resultList) {
// first list - nothing found up till now
this.resultList = foundEntry;
} else {
if (!this.resultList[this.resultList.length - 1]) {
//remove the marker (which was added below/previously)
this.resultList.pop();
}
foundEntry.map(item => this.resultList.push(item));
}
if (this.resultList[0] && this.resultList[0].total > this.resultList.length + 1) {
//some more elements could be fetched from the server ==> add a dummy entry for rendering
this.resultList.push(undefined);
}
// notify the search list to be updated
this.lstSearchResults.next(this.resultList);
and for the scrolling I have the following code:
nextBatch(): void {
if (this.theEnd) {
return;
}
if (this.resultList[0]) {
// something was found
if (this.viewport.getRenderedRange().end === this.viewport.getDataLength()) {
// since we scrolled to the very end of the rendered display
// ==> check if further search is required (and do so...)
const searchTotal = this.resultList[0].total;
this.mySearchConfig.posOffset += this.mySearchConfig.noOfElements;
// some basic check if the total counter exceeds the current offset
// i.e. no further search required
if (this.mySearchConfig.posOffset <= searchTotal) {
this.search(this.mySearchConfig, true);
} else {
this.theEnd = true;
}
}
} else {
// nothing found ==> mark the end
this.theEnd = true;
}
}
answered Jan 8 at 7:41
LeOLeO
688932
688932
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54009818%2fangular-material-virtualscrollviewport-load-data-async%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown