An example of virtualScroll and Infinite Scroll in Ionic 2

An example of virtualScroll and Infinite Scroll in Ionic 2

This post is more than 2 years old.

Before I begin, a warning. At the time I wrote this blog post, Ionic 2 was still in beta. Also, I've barely begun to learn Ionic 2 myself. You should consider this code beta-level quality written by an inexperienced dev. On the other hand, if it still works perfectly, I'm going to pretend I was brilliant all along.

A few days ago, the Ionic blog released a great entry on Ionic 2 and APIs: 10 Minutes with Ionic 2: Calling an API. In this post, Andrew describes how you can use the Ionic CLI to generate both an application and a boilerplate HTTP service, or more accurately, a "Provider".

If you walk through this his tutorial you'll end up with a simple application that drives a list of people via the Random User Generator. I was thinking about how you would take this application and convert it to use an infinite (well, near infinite) list of people instead.

To begin, I used the Random User Generator to output a huge list of users. I didn't want to abuse their API so I did one big call, saved the JSON, and then imported that data into my LoopBack application running locally. (If anyone wants to see how that's done, just ask. I basically reused some of the logic from my blog post: Seeding data for a StrongLoop app). The net result was that I had a lot of "People" data that I could use via a REST API - a bit over two thousand.

My first change was to people-service.ts. The original code would cache the result. My modified code removes this cache and supports a parameter telling it what index to begin fetching results. This is all part of the LoopBack API and I'll be talking about that in a blog post over on the StrongLoop blog later this week.


import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map';

/*
  Generated class for the PeopleService provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular 2 DI.
*/
@Injectable()
export class PeopleService {

  perpage:number = 50;
  
  constructor(public http: Http) {}

  load(start:number=0) {

    return new Promise(resolve => {
      
      this.http.get('http://localhost:3000/api/people?filter[limit]='+this.perpage+'&filter[skip]='+start)
        .map(res => res.json())
        .subscribe(data => {

          resolve(data);

        });
    });
  }
}

Note that I'm hard coding a perpage value. In theory the service could let me change that, but I wanted to keep things somewhat simple.

Now let's look at the Home page. First, let's consider the view.


<ion-navbar *navbar>
	<ion-title>
		Home
	</ion-title>
</ion-navbar>

<ion-content class="home">
	
	<ion-list [virtualScroll]="people" approxItemHeight="50px">
		<ion-item *virtualItem="#person">
			<ion-avatar item-left>
				<ion-img [src]="person.picture" width="48" height="48"></ion-img>
			</ion-avatar>
			<h2>{{person.name}}</h2>
		</ion-item>
	</ion-list>

 <ion-infinite-scroll (infinite)="doInfinite($event)">
   <ion-infinite-scroll-content></ion-infinite-scroll-content>
 </ion-infinite-scroll>
 	
</ion-content>

The changes here are two-fold. First, I changed the list to support VirtualScroll. This is the Ionic V2 version of collectionRepeat, basically a list optimized to handle a butt load of data.

Secondly - I added the InfiniteScroll directive. This is pretty simple to use (you'll see the code in a moment), but don't forget this little gem in the docs: "When this expression has finished its tasks, it should call the complete() method on the infinite scroll instance." Yeah, that's pretty important. But I'll pretend I didn't miss that. Ok, so let's look at the code behind the view.


import {Page} from 'ionic-angular';
import {PeopleService} from '../../providers/people-service/people-service';

@Page({
  templateUrl: 'build/pages/home/home.html',
  providers:[PeopleService]
  
})
export class HomePage {
  public people:any = [];
  private start:number=0;
  
  constructor(public peopleService:PeopleService) {
    
    this.loadPeople();
  }
  
  loadPeople() {
    
    return new Promise(resolve => {
      
      this.peopleService.load(this.start)
      .then(data => {
        
        for(let person of data) {
          this.people.push(person);
        }
        
        resolve(true);
        
      });
            
    });

  }
  
  doInfinite(infiniteScroll:any) {
     console.log('doInfinite, start is currently '+this.start);
     this.start+=50;
     
     this.loadPeople().then(()=>{
       infiniteScroll.complete();
     });
     
  }

}

Ok, so there's a few important changes here. First, I made loadPeople return a promise. I needed this so I could listen for it to complete when running my "get new stuff" code. I'm keeping a variable, start, to know where I am in the list of data and you can see doInfinite as the handler for fetching more data. Pay special attention to the infiniteScroll.complete() call. As the docs say, you need to do this to let the InfiniteScroll control know stuff is done. Also note that this.start+=50 is problematic since 50 has to match the perpage value in the service. I could make it detect how many items were added in the last call, but again, I wanted to keep it simple.

Here is a snazzy animated gif of it in action:

Unfortunately, the code does not appear to work in iOS. I have no idea why (no error is thrown), but it works fine in Android and via ionic serve.

If you want the complete source code for this, I'm more than happy to share it, but bear in mind this is part of a larger LoopBack app. I'll share a link to the entire application, but the Ionic specific stuff may be found in the app1 folder.

https://github.com/cfjedimaster/StrongLoopDemos/tree/master/superlongscroll

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Max, Ionic CEO posted on 5/2/2016 at 5:23 PM

Hey Ray, we're looking on our end to see what's up with iOS...

Comment 2 (In reply to #1) by Raymond Camden posted on 5/2/2016 at 5:23 PM

Coolio. If I just did it wrong (distinct possibility) let me know. :)

Comment 3 by Ross Martin posted on 5/14/2016 at 5:08 PM

Awesome, thank you for this article.

Comment 4 (In reply to #3) by Raymond Camden posted on 5/14/2016 at 5:32 PM

You are most welcome.

Comment 5 by LeRoy Gumede posted on 7/5/2016 at 12:57 PM

:) Great Tutorial

Comment 6 by Pascal Reiemkorb posted on 7/9/2016 at 9:13 AM

Good guide! Sadly vs doesn't quiet work with items that change size after render (e.g. with popping out comments below or a reply box) and also don't handle ion-card well (there's a workaround, but its a bit unclean, manually making them 100% width and adding a 10px spacer below each item)

anyways, thanks for the instructions

Comment 7 (In reply to #6) by Raymond Camden posted on 7/9/2016 at 12:56 PM

Ah - sorry it didn't work in that case - and thanks for the warning.

Comment 8 by Drake Witt posted on 7/13/2016 at 4:02 AM

Just a heads up to others, the <ion-infinite-scroll> directive now uses (ionInfinite) instead of (infinite)

Comment 9 (In reply to #8) by Raymond Camden posted on 7/15/2016 at 12:03 PM

Thanks for the update Drake!

Comment 10 (In reply to #6) by berardo posted on 7/16/2016 at 12:39 AM

As you noticed, looks like there are problems rendering and scrolling content in ion-cards.
I achieve same results avoiding ion-cards to wrap virtualscroll, but extending ion-card style in my ion-list.
Something like:
ion-list.mylist { @extend ion-card; }
I wouldn't consider this workaround unclean :)

Comment 11 by berardo posted on 7/19/2016 at 3:07 PM

Hey Raymond, how have you managed to fix the all the way up auto scroll upon ion-infinite refresh?
According to the oficial docs, virtualscroll doesn't fit well with dynamic lists, and infinite scroll is exactly to dynamically fill in more data.
In my opinion it's something the ionic team should sort out. Lists are presumably dynamic data, and virtualscroll seems the right solution to deal with big lists, which are even more likely to undergo changes.
Moreover, I don't know if you could experience that but this combination of tools looks quite buggy with a bit more complex list templates. I've tried them with ion-item-sliding, ion-item-divider and ion-thumbnails and I got a lot of troubles, specially having multiple thumbnails (mostly two, but sometimes three or so) aleatory rendered in one single element.
They are pretty cool tools, but I'm a bit sad to say that they seem don't get along (just atm, I hope).

Comment 12 (In reply to #11) by Jaafar Abu Sair posted on 8/28/2016 at 1:15 PM

I have the same issue

Comment 13 by Bassam posted on 11/13/2016 at 7:37 AM

why the scrolling in ios not smooth , any is very slow when display long of data .

Comment 14 by Thomas Thai posted on 11/20/2016 at 6:39 PM

Raymond, thank you for the tutorial. In the view for the HomePage, in the content area, you had the virtual scroll listed on top of the infinite scroll. I want to make sure I understand this correctly. These are two different ways of displaying scrolled data. Virtual scroll displays a subset of a larger amount of data. Infinite scroll fetches more data when the user reaches the bottom right? If so, I was expecting to see two displays of those two scrolls when I saw the animation. It confused me when I saw only one.

Comment 15 (In reply to #14) by Raymond Camden posted on 11/21/2016 at 2:48 PM

Yep. One is handling the display, the other is handling knowing when you are at the bottom and fetching more. infinitescrollcontent is where a loading message would display.

Comment 16 by lrlarson posted on 12/1/2016 at 5:34 PM

I think that <ion-img> does not work in IOS. The infinite scroll does work in IOS with simple <img> tags in the <ion-item> content.

Comment 17 (In reply to #16) by Raymond Camden posted on 12/1/2016 at 5:43 PM

Please be sure to file a bug report.

Comment 18 by Muhammad Habib Ali posted on 12/28/2016 at 1:23 PM

What about disabling infinite scroll when the data has been ended. there is enable(false) instance member. but i don't know how to use it. can you please make its demo so that it could be easy to use. Thanks in advance

Comment 19 (In reply to #18) by Raymond Camden posted on 12/28/2016 at 2:11 PM

If I had to guess, add an id to the infinite scroll component, like #myScroll, then in your code you myScroll.enable=false.

Comment 20 (In reply to #1) by Hugo Bessa posted on 1/5/2017 at 5:34 AM

Max, Ionic CEO have you found any clue about this?

Comment 21 by Nikita Soloman posted on 3/2/2017 at 2:12 PM

hello sir ,
my response is Response {_body: "[{"outletId":367,"name":"Bluet - Best Western Hote…ull,"day":"SAT","parentOutletTimingSlab":null}]}]", status: 200, ok: true, statusText: "OK", headers: Headers…} and (data.json()=[Object, Object, Object, Object, Object...]) .
I was using "this.item=JSON.parse(data['_body']);" to set my data to list .I tried to set this data to
"for(let person of data) {
this.people.push(person);
}"
code of your's but it is showing me error that [ts] Type '{}' is not an array type or a string type.
(parameter) data: {}
. Please help me sir... I'am a beginner in ionic 2

Comment 22 (In reply to #21) by Raymond Camden posted on 3/2/2017 at 3:12 PM

If your provider isn't returning an array of objects, I'd look at that. In my code, you can see how I convert the JSON to regular data with the res.json() call.

Comment 23 (In reply to #22) by Nikita Soloman posted on 3/3/2017 at 7:05 AM

hello sir thank you for your prompt reply.
I found the solution with the help of my manager :) and thank you for the tutorial it was really helpful...

Comment 24 (In reply to #23) by Eye Sweat posted on 3/28/2017 at 12:08 PM

Hi Nikita, I have the same issue, how did you fix it ?

Comment 25 (In reply to #19) by Dennis posted on 4/25/2017 at 9:11 PM

Just call a function within the attribute shouldEnable.
For example shouldEnable="hasMoreData()".
In the function you check if there is more data to fetch

Comment 26 (In reply to #24) by nacer alidrissi posted on 5/8/2017 at 4:51 PM

Hello,
I resolved that like this:
in the PeopleService provider I changed
load(start:number=0)
to
load(start:number=0):Promise<[any]>
.

And thank you Raymond Camden for this tuto. it was very helpful.

Comment 27 by hrdk mehta posted on 5/11/2017 at 2:15 PM

Hello sir,
thank you for this article, do you have any idea how to use virtual scroll with masonry library in ionic 2?

Comment 28 (In reply to #27) by Raymond Camden posted on 5/12/2017 at 1:10 AM

Sorry no.

Comment 29 (In reply to #26) by Rizwana Nusrat posted on 7/14/2017 at 6:48 AM

ya It's working..Thank you so much

Comment 30 by Harshad Dusane posted on 8/17/2017 at 7:15 AM

hello sir,
thanks for article,
I try this but *virtualItem="#person" showing error
how can i fix this issue to implement infinite scroll.
thanks in advance.

Comment 31 (In reply to #30) by Raymond Camden posted on 8/17/2017 at 12:14 PM

What error?

Comment 32 (In reply to #30) by Jesús Montes 🤓 💻 posted on 8/18/2017 at 7:54 AM

You should use "let" instead of "#", like this:

*virtualItem="let person"

Comment 33 (In reply to #32) by Harshad Dusane posted on 8/18/2017 at 7:57 AM

thanks for reply sir, issue was solved.

Comment 34 (In reply to #31) by Harshad Dusane posted on 8/18/2017 at 7:58 AM

issue solved sir thanks

Comment 35 (In reply to #32) by Raymond Camden posted on 8/18/2017 at 12:01 PM

Thanks Jesus - was totally booked yesterday.

Comment 36 by Danura Aditya posted on 9/15/2017 at 3:07 AM

thats working But if i'am using any its not work just remove any

doInfinite(infiniteScroll:any) {

doInfinite(infiniteScroll) {

Thanks mate