Introduction
When dealing with large collections of items (thousands, upwards of tens of thousands), whether that be an array or map, certain challenges come with displaying those items in a performant manner due to DOM constraints. This is where the UI Virtualization plugin comes in very handy.
This plugin enables "virtualization" of lists through a new virtual-repeat.for attribute. When used, the list "virtually" has tens or hundreds of thousands of rows, but the DOM only has rows for what is visible. This allows rendering of massive lists of data with amazing performance. It works like repeat.for, it just creates a scrolling area and manages the list using UI virtualization techniques.
Installing The Plugin
If you are using the CLI or Webpack, you can install the plugin from NPM:
npm install aurelia-ui-virtualization
or
yarn add aurelia-ui-virtualization
If you are using JSPM for client dependencies, then you can use this command:
jspm install aurelia-ui-virtualization
Configuring The Plugin
In your main.js
within your src
folder, simply call the plugin API with the animation plugin's name:
import { PLATFORM } from 'aurelia-pal';
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin(PLATFORM.moduleName('aurelia-ui-virtualization')); //<-- add this
aurelia.start().then(a => a.setRoot());
}
PLATFORM.moduleName
is only required if you are using Webpack.
Using The Plugin
If you have used Aurelia's out-of-the-box repeat.for
attribute, then you will probably notice the UI Virtualization library provides its own similar repeater called virtual-repeat.for
which uses the exact same syntax.
Just like you can with the standard repeat.for
attribute, the virtual-repeat.for
attribute allows you to repeat any kind of content in your views.
The rows being repeated need a fixed height and only one item per row. The virtualization requires all elements are the exact same height
Basic repeat
<template>
<div virtual-repeat.for="item of items">
${$index} ${item}
</div>
</template>
Unordered list repeat
<template>
<ul>
<li virtual-repeat.for="item of items">
${$index} ${item}
</li>
</ul>
</template>
Table row repeat
<template>
<table>
<tr virtual-repeat.for="item of items">
<td>${$index}</td>
<td>${item}</td>
</tr>
</table>
</template>
Infinite Scroll
The UI Virtualization plugin allows you to virtually scroll lists comprised of many items, it also provides an infinite scroll attribute which allows you to fetch more items when the user scrolls the container. The infinite-scroll-next
attribute accepts a callback function in your view which receives three arguments when fired.
- Argument #1 An integer value that represents the current item that exists at the top of the rendered items in the DOM.
- Argument #2 A boolean value that indicates whether the list has been scrolled to the bottom of the items list.
- Argument #3 A boolean value that indicates whether the list has been scrolled to the top of the items list.
<template>
<div virtual-repeat.for="item of items" infinite-scroll-next="getMore">
${$index} ${item}
</div>
</template>
export class App {
items = ['Foo', 'Bar', 'Baz'];
getMore(topIndex, isAtBottom, isAtTop) {
for(let i = 0; i < 100; ++i) {
this.items.push('item' + i);
}
}
}
Similarly, the infinite-scroll-next
attribute also supports using an expression via .call
<template>
<div virtual-repeat.for="item of items" infinite-scroll-next.call="getMore($scrollContext)">
${$index} ${item}
</div>
</template>
export class App {
items = ['Foo', 'Bar', 'Baz'];
getMore(scrollContext) {
for(let i = 0; i < 100; ++i) {
this.items.push('item' + i);
}
}
}
The infinite-scroll-next attribute can accept a function, a promise, or a function that returns a promise. It is quite flexible in how it allows you to implement infinite loading functionality into your Aurelia application.
Caveats
<template></template>
is not supported as a root element of a virtual repeat template, because virtualization requires item heights to be calculatable. With<template></template>
, there is no easy and performant way to acquire this value.- Similar to (1), other template controllers cannot be used in conjunction with
virtual-repeat
, unlike repeat i.e: built-in template controllers:with
,if
,replaceable
cannot be used withvirtual-repeat
. You can easily work around this constraint by nesting other template controllers inside the repeated element, with<template></template>
element, for example:
<template>
<h1>${message}</h1>
<div virtual-repeat.for="person of persons">
<template with.bind="person">
${Name}
</template>
</div>
</template>
- Beware of CSS selector
:nth-child
and similar selectors. Virtualization requires appropriate removing and inserting visible items, based on scroll position. This means DOM elements order will not stay the same, thus creating unexpected:nth-child
CSS selector behavior. To work around this, you can use contextual properties$index
,$odd
,$even
etc... to determine an item position, and apply CSS classes/styles against it, like the following example:
<template>
<div virtual-repeat.for="person of persons" class="${$odd ? 'odd' : 'even'}-row">
${person.name}
</div>
</template>
- Similar to (3), virtualization requires appropriate removing and inserting visible items, corresponding lifecycle of items view will also be triggered while inserting (
bind
,attached
) or removing (unbind
,detached
)