Support Dynamic Multiple Columns And Infinity Scroll in Ionic Angular
timsar2
Posted on February 8, 2021
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 a virtual scroll, But it does 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 column 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 items is in the list and calculate their index, After that we can use virtual-list index to retrieve data from the list.
As you can see below, Extra columns will be added to the virtual list.
Let’s do it!
First Create an ionic angular project and then install faker for mocking data.
ionic start virtual-columns tabs
npm install faker --save
npm install @types/faker --save
inside your .ts file import faker
import * as faker from 'faker';
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;
}
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
}
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;
...
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);
}
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;
}
}
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">
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>
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);
}
}
Tab3PageModule:
import { PipesModule } from '../pipes/pipes.module';
@NgModule({
imports: [
PipesModule,
...
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>
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;
}
}
try to resize screen from stackblitz
It was useful for my project, I hope you liked it 😏
Posted on February 8, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.