VirtualScrollerVirtualScroller is an efficient way of rendering lists by displaying a small subset of data in the viewport at any time.

VirtualScroller - 图1

Documentation

CDK

VirtualScrolling depends on @angular/cdk's ScrollingModule so begin with installing CDK if not already installed.

  1. npm install @angular/cdk --save
  2.  

Import

  1. import {VirtualScrollerModule} from 'primeng/virtualscroller';
  2.  

Getting Started

VirtualScroller requires a collection of items as its value, height of an item size, height of the scrollable viewport and a ng-template to display where each item can be accessed using the implicit variable.

Throughout the samples, a car interface having vin, brand, year and color properties are used to define an object to be displayed by the VirtualScroller. Cars are loaded by a CarService that connects to a server to fetch the cars with a Promise. Note that this is for demo purposes only, any data source such as an Observable can be used as an alternative as well.

  1. export interface Car {
  2. vin;
  3. year;
  4. brand;
  5. color;
  6. }
  7.  
  1. import { HttpClient } from '@angular/common/http';
  2. import { Injectable } from '@angular/core';
  3. import { Car } from '../domain/car';
  4. @Injectable()
  5. export class CarService {
  6. constructor(private http: HttpClient) {}
  7. getCarsSmall() {
  8. return this.http.get('/showcase/resources/data/cars-small.json')
  9. .toPromise()
  10. .then(res => <Car[]> res.data)
  11. .then(data => { return data; });
  12. }
  13. }
  14.  

Here is a sample VirtualScroller that displays a list of cars loaded from a remote datasource.

  1. export class VirtualScrollerDemo implements OnInit {
  2. cars: Car[];
  3. constructor(private carService: CarService) { }
  4. ngOnInit() {
  5. this.carService.getCarsLarge().then(cars => this.cars = cars);
  6. }
  7. }
  8.  
  1. <p-virtualScroller [value]="cars" scrollHeight="500px" [itemSize]="150">
  2. <ng-template pTemplate="item" let-car>
  3. Car content
  4. </ng-template>
  5. </p-virtualScroller>
  6.  

Sections

Header and Footer are the two sections that are capable of displaying custom content.

  1. <p-virtualScroller [value]="cars" scrollHeight="500px" [itemSize]="150">
  2. <p-header>Header Content</p-header>
  3. <p-footer>Footer Content</p-footer>
  4. <ng-template pTemplate="item" let-car>
  5. Car content
  6. </ng-template>
  7. </p-virtualScroller>
  8.  

Lazy Loading

Lazy mode is handy to deal with large datasets where instead of loading the entire data, small chunks of data are loaded on demand by invoking onLazyLoad callback everytime scrolling requires a new chunk. To implement lazy loading, enable lazy attribute, initialize the number of logical rows with a query and finally implement a method callback using onLazyLoad that actually loads a chunk from a datasource. onLazyLoad gets an event object that contains information about the chunk of data to load such as the index and number of items to load. Notice that a new template called loadingItem is also required to display as a placeholder while the new items are being loaded.

  1. <p-virtualScroller [value]="lazyCars" scrollHeight="500px" [itemSize]="150" [rows]="100" [cache]="false"
  2. [lazy]="true" (onLazyLoad)="loadCarsLazy($event)" [totalRecords]="totalLazyCarsLength">
  3. <ng-template let-car pTemplate="item">
  4. Car content
  5. </ng-template>
  6. <ng-template let-car pTemplate="loadingItem">
  7. Loading...
  8. </ng-template>
  9. </p-virtualScroller>
  10.  
  1. loadData(event) {
  2. //event.first = First row offset
  3. //event.rows = Number of rows per page
  4. //this.lazyCars = load new chunk between first index and (first + rows) last index
  5. }
  6.  

Properties

NameTypeDefaultDescription
valuearraynullAn array of objects to display.
itemSizenumbernullHeight of an item in the list.
rowsnumbernullNumber of rows to display per page.
firstnumber0Index of the first row to be displayed.
totalRecordsnumbernullNumber of total records, defaults to length of value when not defined.
lazybooleanfalseDefines if data is loaded and interacted with in lazy manner.
scrollHeightanynullMax height of the content area in inline mode.
stylestringnullInline style of the component.
styleClassstringnullStyle class of the component.
trackByFunctionnullFunction to optimize the dom operations by delegating to ngForTrackBy, default algoritm checks for object identity.

Events

NameParametersDescription
onLazyLoadevent.first = First row offset event.rows = Number of rows per page Callback to invoke in lazy mode to load new data.

Methods

NameParametersDescription
scrollToindex: Index of the itemScrolls to the item with the given index.

Styling

Following is the list of structural style classes, for theming classes visit theming page.

NameElement
ui-virtualscrollerContainer element.
ui-virtualscroller-headerHeader section.
ui-virtualscroller-footerFooter section.
ui-virtualscroller-contentContent section.
ui-virtualscroller-listList element.

Dependencies

Angular CDK.

Source

View on GitHub

  1. <h3 class="first">Lazy Loading</h3>
  2. <p-virtualScroller [value]="lazyCars" scrollHeight="500px" [itemSize]="150" [rows]="100" [cache]="false"
  3. [lazy]="true" (onLazyLoad)="loadCarsLazy($event)" [totalRecords]="totalLazyCarsLength">
  4. <p-header>
  5. List of Cars
  6. </p-header>
  7. <ng-template let-car pTemplate="item" let-i="index">
  8. <div class="ui-g car-item">
  9. <div class="ui-g-12 ui-md-2">
  10. <div style="font-size: 24px; text-align: center; padding-top: 48px">{{i}}</div>
  11. </div>
  12. <div class="ui-g-12 ui-md-2">
  13. <img src="assets/showcase/images/demo/car/{{car?.brand}}.png">
  14. </div>
  15. <div class="ui-g-12 ui-md-8">
  16. <div class="ui-g">
  17. <div class="ui-g-2 ui-sm-6">Vin: </div>
  18. <div class="ui-g-10 ui-sm-6">{{car?.vin}}</div>
  19. <div class="ui-g-2 ui-sm-6">Year: </div>
  20. <div class="ui-g-10 ui-sm-6">{{car?.year}}</div>
  21. <div class="ui-g-2 ui-sm-6">Brand: </div>
  22. <div class="ui-g-10 ui-sm-6">{{car?.brand}}</div>
  23. <div class="ui-g-2 ui-sm-6">Color: </div>
  24. <div class="ui-g-10 ui-sm-6">{{car?.color}}</div>
  25. </div>
  26. </div>
  27. </div>
  28. </ng-template>
  29. <ng-template let-car pTemplate="loadingItem">
  30. <div class="ui-g car-item empty-car-item">
  31. <div class="ui-g-12 ui-md-2">
  32. <div class="empty-car-item-index"></div>
  33. </div>
  34. <div class="ui-g-12 ui-md-2">
  35. <div class="empty-car-item-image"></div>
  36. </div>
  37. <div class="ui-g-12 ui-md-8">
  38. <div class="ui-g">
  39. <div class="ui-g-12"><div class="empty-car-item-text"></div></div>
  40. <div class="ui-g-12"><div class="empty-car-item-text"></div></div>
  41. <div class="ui-g-12"><div class="empty-car-item-text"></div></div>
  42. <div class="ui-g-12"><div class="empty-car-item-text"></div></div>
  43. </div>
  44. </div>
  45. </div>
  46. </ng-template>
  47. </p-virtualScroller>
  48. <h3>Prepopulated List</h3>
  49. <p-virtualScroller [value]="cars" scrollHeight="500px" [itemSize]="150">
  50. <p-header>
  51. <div class="ui-g">
  52. <div class="ui-g-6 sort-container">
  53. <p-dropdown [options]="sortOptions" [(ngModel)]="sortKey" placeholder="Sort By" (onChange)="onSortChange()" [style]="{'min-width':'140px'}"></p-dropdown>
  54. </div>
  55. <div class="ui-g-6 title-container">
  56. List of Cars
  57. </div>
  58. </div>
  59. </p-header>
  60. <ng-template let-car pTemplate="item" let-i="index">
  61. <div class="ui-g car-item">
  62. <div class="ui-g-12 ui-md-2">
  63. <div style="font-size: 24px; text-align: center; padding-top: 48px">{{i}}</div>
  64. </div>
  65. <div class="ui-g-12 ui-md-2">
  66. <img src="assets/showcase/images/demo/car/{{car?.brand}}.png">
  67. </div>
  68. <div class="ui-g-12 ui-md-8">
  69. <div class="ui-g">
  70. <div class="ui-g-2 ui-sm-6">Vin: </div>
  71. <div class="ui-g-10 ui-sm-6">{{car?.vin}}</div>
  72. <div class="ui-g-2 ui-sm-6">Year: </div>
  73. <div class="ui-g-10 ui-sm-6">{{car?.year}}</div>
  74. <div class="ui-g-2 ui-sm-6">Brand: </div>
  75. <div class="ui-g-10 ui-sm-6">{{car?.brand}}</div>
  76. <div class="ui-g-2 ui-sm-6">Color: </div>
  77. <div class="ui-g-10 ui-sm-6">{{car?.color}}</div>
  78. </div>
  79. </div>
  80. </div>
  81. </ng-template>
  82. </p-virtualScroller>
  83.  
  1. @Component({
  2. templateUrl: './virtualscrollerdemo.html',
  3. styles: [`
  4. .car-item .ui-md-3 {
  5. text-align: center;
  6. }
  7. .car-item .ui-g-10 {
  8. font-weight: bold;
  9. }
  10. .empty-car-item-index {
  11. background-color: #f1f1f1;
  12. width: 60px;
  13. height: 60px;
  14. margin: 36px auto 0 auto;
  15. animation: pulse 1s infinite ease-in-out;
  16. }
  17. .empty-car-item-image {
  18. background-color: #f1f1f1;
  19. width: 120px;
  20. height: 120px;
  21. animation: pulse 1s infinite ease-in-out;
  22. }
  23. .empty-car-item-text {
  24. background-color: #f1f1f1;
  25. height: 18px;
  26. animation: pulse 1s infinite ease-in-out;
  27. }
  28. .title-container {
  29. padding: 1em;
  30. text-align: right;
  31. }
  32. .sort-container {
  33. text-align: left;
  34. }
  35. @media (max-width: 40em) {
  36. .car-item {
  37. text-align: center;
  38. }
  39. }
  40. `]
  41. })
  42. export class VirtualScrollerDemo implements OnInit {
  43. cars: Car[] = [];
  44. lazyCars: Car[];
  45. brands: string[];
  46. colors: string[];
  47. totalLazyCarsLength: number;
  48. timeout: any;
  49. sortKey: string;
  50. sortOptions: SelectItem[];
  51. constructor(private carService: CarService) { }
  52. ngOnInit() {
  53. this.brands = [
  54. 'Audi', 'BMW', 'Fiat', 'Ford', 'Honda', 'Jaguar', 'Mercedes', 'Renault', 'Volvo', 'VW'
  55. ];
  56. this.colors = [
  57. 'Black', 'White', 'Red', 'Blue', 'Silver', 'Green', 'Yellow'
  58. ];
  59. for (let i = 0; i < 10000; i++) {
  60. this.cars.push(this.generateCar());
  61. }
  62. //in a real application, make a remote request to retrieve the number of records only, not the actual records
  63. this.totalLazyCarsLength = 10000;
  64. this.sortOptions = [
  65. {label: 'Newest First', value: '!year'},
  66. {label: 'Oldest First', value: 'year'}
  67. ];
  68. }
  69. generateCar(): Car {
  70. return {
  71. vin: this.generateVin(),
  72. brand: this.generateBrand(),
  73. color: this.generateColor(),
  74. year: this.generateYear()
  75. }
  76. }
  77. generateVin() {
  78. let text = "";
  79. let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  80. for (var i = 0; i < 5; i++) {
  81. text += possible.charAt(Math.floor(Math.random() * possible.length));
  82. }
  83. return text;
  84. }
  85. generateBrand() {
  86. return this.brands[Math.floor(Math.random() * Math.floor(10))];
  87. }
  88. generateColor() {
  89. return this.colors[Math.floor(Math.random() * Math.floor(7))];
  90. }
  91. generateYear() {
  92. return 2000 + Math.floor(Math.random() * Math.floor(19));
  93. }
  94. loadCarsLazy(event: LazyLoadEvent) {
  95. //in a real application, make a remote request to load data using state metadata from event
  96. //event.first = First row offset
  97. //event.rows = Number of rows per page
  98. //imitate db connection over a network
  99. if (this.timeout) {
  100. clearTimeout(this.timeout);
  101. }
  102. this.timeout = setTimeout(() => {
  103. this.lazyCars = [];
  104. if (this.cars) {
  105. this.lazyCars = this.cars.slice(event.first, (event.first + event.rows));
  106. }
  107. }, 1000);
  108. }
  109. onSortChange() {
  110. if (this.sortKey.indexOf('!') === 0)
  111. this.sort(-1);
  112. else
  113. this.sort(1);
  114. }
  115. sort(order: number): void {
  116. let cars = [...this.cars];
  117. cars.sort((data1, data2) => {
  118. let value1 = data1.year;
  119. let value2 = data2.year;
  120. let result = (value1 < value2) ? -1 : (value1 > value2) ? 1 : 0;
  121. return (order * result);
  122. });
  123. this.cars = cars;
  124. }
  125. }
  126.