DEV Community

loading...
Cover image for How Ionic Virtual Scroll Support Dynamic Multiple Columns And Infinity Scroll

How Ionic Virtual Scroll Support Dynamic Multiple Columns And Infinity Scroll

timsar2 profile image timsar2 Updated on ・4 min read

Hello friends,
In my latest Project I Found My product list is so large and it’s slow on response. So I was going to use virtual scroll, But it dose not support more than one column or dynamic multiple columns.
So I tried to found a way to make ion-virtual-scroll to support more than one columns and maybe dynamic columns.

ion-virtual-scroll has an item list like [items]=”list”, and it’ll iterate over the list. To support multiple columns we must find how many item is in list and calculate their index, After that we can use virtual-list index to retrieve data from the list.
As you can see below, Extera columns will be added to virtual list.

Alt Text
Alt Text

Let’s do it!

First Create an ionic angular project and then install faker for mocking data.

ionic start virtual-columns tabs
Enter fullscreen mode Exit fullscreen mode

npm install faker --save
npm install @types/faker --save

inside your .ts file import faker

import * as faker from 'faker';
Enter fullscreen mode Exit fullscreen mode

Get List of Data from faker package:

getEmployees() {
    for (let i = 0; i < 250; i++) {
      this.dataList.push({
        id: this.dataList.length,
        image: faker.image.image(),
        name: faker.name.firstName(),
        address: faker.address.streetAddress(),
        intro: faker.lorem.words()
      });
    }
    // for triggering pipe when data source changed //
    this.nextPipe = this.dataList.length;
  }
Enter fullscreen mode Exit fullscreen mode

Listen to Screen resize and calculate how many extra column must add to the list:

@HostListener('window:resize', ['$event'])
  getScreenSize(event?) {
    this.screenWidth = window.innerWidth;
    this.exteraCol = Math.trunc(this.screenWidth / this.vColMinWidth) -1;
    this.exteraCol = this.exteraCol < 0 ? 0 : this.exteraCol;
    this.exteraCol = this.exteraCol > 3 ? 3 : this.exteraCol; // if we want to have max virtual column count
  }
Enter fullscreen mode Exit fullscreen mode

Import virtualScroll and infinity ViewChild to .ts file, To detect and manage when scroll touch end.

export class Tab3Page {
  @ViewChild(IonVirtualScroll) virtualScroll: IonVirtualScroll;
  @ViewChild(IonInfiniteScroll) infiniteScroll: IonInfiniteScroll;
...
Enter fullscreen mode Exit fullscreen mode

Make it infinity scroll:

loadData(event: any) {
    setTimeout(() => {

      // load more data
      this.getEmployees();
      this.virtualScroll.checkEnd();  // trigger end of virtual list
      event.target.complete();

      if (this.dataList.length === 1000) {
        event.target.disabled = true;
      }
    }, 500);
  }
Enter fullscreen mode Exit fullscreen mode

We must to know Min Width of virtual columns, so set vColMinWidth as much as you need.

  • For me it is vColMinWidth = 360;

Generate a pipe with ionic cli command

ionic generate pipe pipes/virtual-list-index

Add VirtualListIndexPipe to PipesModule
Fetch item’s index of dataList with pipe like this:

export class VirtualListIndexPipe implements PipeTransform {

  transform(array: Array<any>, exteraCol: number, nextPipe: number): number[][] {
    const range = (s: number, e: number) => e > s ? [s, ...range(s + 1, e)] : [s];
    const virtualList = [] as number[][];

    let i = 0;
    for (i; i < (array.length - exteraCol); i += exteraCol + 1){
      virtualList.push([...range(i, i + exteraCol)]);
    }
    // last 2d array maybe not full lenght
    virtualList.push([...range(i, array.length - 1)]);

    return virtualList;
  }
}
Enter fullscreen mode Exit fullscreen mode

Import PipesModule to your component module, And use that pipe in your virtual scroll items like this:

<ion-virtual-scroll approxItemHeight="62.4px" [itemHeight]="itemHeightFn"
  [items]="dataList | virtualListIndex: exteraCol : nextPipe">
Enter fullscreen mode Exit fullscreen mode

Now our virtual list items has the value of datalist items refrence and we can use it to get data from dataList

<div *virtualItem="let item;let i = index">
    <ion-row class="ion-nowrap>
        <ion-col *ngFor="let j of item;">
            <h3>i = {{dataList[j].id}} </h3>
        </ion-col>
    </ion-row>
</div>
Enter fullscreen mode Exit fullscreen mode

Complete File's should be like this:

tab3.ts File

import { Component, ViewChild } from '@angular/core';
import { IonInfiniteScroll, IonVirtualScroll } from '@ionic/angular';
import * as faker from 'faker';
import { HostListener } from '@angular/core';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {
  @ViewChild(IonVirtualScroll) virtualScroll: IonVirtualScroll;
  @ViewChild(IonInfiniteScroll) infiniteScroll: IonInfiniteScroll;

  dataList = [];
  vColMinWidth = 360; // virtual list columns min width as pixel
  exteraCol = 0;  // how many columns should be add to virtual list
  nextPipe = 0;
  screenWidth: number;

  constructor() {
    this.getScreenSize();
    this.getEmployees();
  }

  @HostListener('window:resize', ['$event'])
  getScreenSize(event?) {
    this.screenWidth = window.innerWidth;
    this.exteraCol = Math.trunc(this.screenWidth / this.vColMinWidth) -1;
    this.exteraCol = this.exteraCol < 0 ? 0 : this.exteraCol;
    this.exteraCol = this.exteraCol > 3 ? 3 : this.exteraCol; // if we want to have max virtual column count
  }

  getEmployees() {
    for (let i = 0; i < 250; i++) {
      this.dataList.push({
        id: this.dataList.length,
        image: faker.image.image(),
        name: faker.name.firstName(),
        address: faker.address.streetAddress(),
        intro: faker.lorem.words()
      });
    }
    this.nextPipe = this.dataList.length;
  }

  itemHeightFn(item, index) { // better performance if setting item height
    return 62.4;
  }

  loadData(event: any) {
    setTimeout(() => {

      // load more data
      this.getEmployees();
      this.virtualScroll.checkEnd();  // trigger end of virtual list
      event.target.complete();

      if (this.dataList.length === 1000) {
        event.target.disabled = true;
      }
    }, 500);
  }
}

Enter fullscreen mode Exit fullscreen mode

Tab3PageModule:

import { PipesModule } from '../pipes/pipes.module';

@NgModule({
  imports: [
    PipesModule,
...
Enter fullscreen mode Exit fullscreen mode

tab3.html file

<ion-content [fullscreen]="true" class="ion-padding">
  <ion-virtual-scroll approxItemHeight="62.4px" [itemHeight]="itemHeightFn"
    [items]="dataList | virtualListIndex: exteraCol : nextPipe">
    <div *virtualItem="let item;let i = index">
      <ion-row class="ion-nowrap>
        <ion-col *ngFor="let j of item;">
          <h3>i = {{dataList[j].id}} </h3>
        </ion-col>
      </ion-row>
    </div>
  </ion-virtual-scroll>

  <ion-infinite-scroll threshold="150px" (ionInfinite)="loadData($event)">
    <ion-infinite-scroll-content loadingSpinner="bubbles" loadingText="Loading more data...">
    </ion-infinite-scroll-content>
  </ion-infinite-scroll>
</ion-content>
Enter fullscreen mode Exit fullscreen mode

VirtualListIndexPipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'virtualListIndex'
})
export class VirtualListIndexPipe implements PipeTransform {

  transform(array: Array<any>, exteraCol: number, nextPipe: number): number[][] {
    const range = (s: number, e: number) => e > s ? [s, ...range(s + 1, e)] : [s];
    const virtualList = [] as number[][];

    let i = 0;
    for (i; i < (array.length - exteraCol); i += exteraCol + 1){
      virtualList.push([...range(i, i + exteraCol)]);
    }
    // last 2d array maybe not full lenght
    virtualList.push([...range(i, array.length - 1)]);

    return virtualList;
  }
}
Enter fullscreen mode Exit fullscreen mode

try to resize screen from stackblitz

It was useful for my project, I hope you liked it 😏

Discussion (0)

pic
Editor guide