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
Archived Comments
Hey Ray, we're looking on our end to see what's up with iOS...
Coolio. If I just did it wrong (distinct possibility) let me know. :)
Awesome, thank you for this article.
You are most welcome.
:) Great Tutorial
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
Ah - sorry it didn't work in that case - and thanks for the warning.
Just a heads up to others, the <ion-infinite-scroll> directive now uses (ionInfinite) instead of (infinite)
Thanks for the update Drake!
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 :)
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).
I have the same issue
why the scrolling in ios not smooth , any is very slow when display long of data .
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.
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.
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.
Please be sure to file a bug report.
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
If I had to guess, add an id to the infinite scroll component, like #myScroll, then in your code you myScroll.enable=false.
Max, Ionic CEO have you found any clue about this?
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
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.
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...
Hi Nikita, I have the same issue, how did you fix it ?
Just call a function within the attribute shouldEnable.
For example shouldEnable="hasMoreData()".
In the function you check if there is more data to fetch
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.
Hello sir,
thank you for this article, do you have any idea how to use virtual scroll with masonry library in ionic 2?
Sorry no.
ya It's working..Thank you so much
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.
What error?
You should use "let" instead of "#", like this:
*virtualItem="let person"
thanks for reply sir, issue was solved.
issue solved sir thanks
Thanks Jesus - was totally booked yesterday.
thats working But if i'am using any its not work just remove any
doInfinite(infiniteScroll:any) {
doInfinite(infiniteScroll) {
Thanks mate