Get the Newsletter

Virtualization

The basics of using the ui-virtualization plugin for Aurelia.

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 --save
  
  

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

  1. <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.
  2. Similar to (1), other template controllers cannot be used in conjunction with 1virtual-repeat1, unlike repeat i.e: built-in template controllers: with, if, replaceable cannot be used with virtual-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>
  
  
  1. 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>
  
  
  1. 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)