ionic-framework icon indicating copy to clipboard operation
ionic-framework copied to clipboard

bug: Infinite scroll not always working when few items are loaded

Open rveerd opened this issue 5 years ago • 9 comments

Bug Report

Ionic version:

[x] 4.x

Current behavior: When using infinite scroll:

  • If you initially load not enough items to fill the page and to enable scrolling, ionInfinite will never fire. You have to resize the browser window.
  • If you scroll down quickly, ionInfinite does not always fire, especially when you load only few additional items and/or the threshold is set to a high value. You have to scroll up and back down.

This is an issue because:

  • You might want to load as few items as possible, especially if the load time is high or because of bandwidth/costs.
  • You never now how many items are enough to fill the page, because of screen size, font size, etc..

Expected behavior: I expect ionInfinite to fire when more items can be shown on the page.

Related code:

https://github.com/rveerd/ionic4-issue-infinitie-scroll-loading-few-items

Other information:

This problem has already been reported for Ionic 1/2/3 as #661, 4980 and #6341.

Ionic info:

Ionic:

   ionic (Ionic CLI)             : 4.11.0
   Ionic Framework               : @ionic/angular 4.3.0
   @angular-devkit/build-angular : 0.13.8
   @angular-devkit/schematics    : 7.2.4
   @angular/cli                  : 7.3.8
   @ionic/angular-toolkit        : 1.4.1

System:

   NodeJS : v10.15.0
   npm    : 6.4.1
   OS     : Windows Server 2016

rveerd avatar Apr 18 '19 09:04 rveerd

Happy to look into this one, we also ran into this and for now just used a very large default page size, but it would be nice if the component invoked the infinite method until it fills the viewable area. The important part is to make sure it stops requesting if the list doesn’t grow (there truly are no more items)

daem0ndev avatar Apr 24 '19 03:04 daem0ndev

Any news on this? This bug is unsolved since ionic 2.x I think :(

Chris1234567899 avatar Nov 13 '19 19:11 Chris1234567899

will this be fixed with ionic 6?

fr3fou avatar Jul 26 '21 22:07 fr3fou

Just encountered this with v6.1.3 with ionic-vue. While in dev mode with a browser one can shrink the browser to create a scrollbar to trigger the scroller but this trick will not work when the app is deployed (only tested android). Anyone have a workaround to trigger the scroll for a deployed app if only a few items are on the page?

acommend avatar Apr 28 '22 23:04 acommend

I wrote a hacky workaround back then - use at your own risk:

import {   Directive,  ElementRef,  EventEmitter,    HostListener, Input, OnChanges, Output } from '@angular/core';

@Directive({
  selector: '[infiniteFix]',
})
export class InfiniteScrollFixDirective implements OnChanges {

  @Output()ionInfinite = new EventEmitter();
  @Input("infiniteFix")infiniteFix:any[];

  previousLength;
  constructor(private element: ElementRef){
  }

  @HostListener('window:resize', ['$event'])
  onResize(ev) {
    this.checkLoad();
  }

  ngOnChanges(){
    if(!this.infiniteFix) return;

    if(!this.previousLength)
      this.previousLength = this.infiniteFix.length;

    if(this.infiniteFix && this.infiniteFix.length>0 && this.infiniteFix.length != this.previousLength)
      this.checkLoad();
      this.previousLength = this.infiniteFix.length;
  }

  checkLoad() {
    this.checkForScrollbar().then(res => {
      if (!res) {
        //console.log('infinite no scrollbar, trying to load more data');
        this.ionInfinite.emit()
      } else {
        //console.log('infinite yes scrollbar, we are good');
      }
    });
  }

  async checkForScrollbar(): Promise<boolean> {
    const contentEl = this.element.nativeElement.closest('ion-content');
    let scrollEl = await contentEl.getScrollElement();
    return scrollEl.scrollHeight > scrollEl.clientHeight;
  }
}

Just add the directive to your ion-infinite-scroll

<ion-list>
    <ion-item *ngFor="let item of items; let i = index;">
      {{i}} item
    </ion-item>
  </ion-list>
  <ion-infinite-scroll [infiniteFix]="items" (ionInfinite)="doInfinite($event)">
      <ion-infinite-scroll-content loadingSpinner="bubbles"></ion-infinite-scroll-content>
  </ion-infinite-scroll>

Probably not the nicest solution, but better than nothing.

Chris1234567899 avatar Apr 29 '22 00:04 Chris1234567899

@Chris1234567899 thanks for the workaround but unfortunately it won't work in my use case as each scroll action is associated with a chunk of time which may or may not have any items for that time range. Moreover I do not know the total number of items. If a scroll bar is present I have the component working to pull the next chunk of data for the next time range however if no scroll bar exists or the scrollbar that exists doesn't pass the threshold check the component will do nothing. One thought I had of was using the ion-refresher component for my use case however it doesn't support an option to use it at the bottom of the page. I may end up just adding a fab to trigger loading the next chunk of data instead of using the infinite scroll component or if time permits work with the team providing the API I'm using to get data to add a method to page the data by number of items instead.

acommend avatar Apr 29 '22 17:04 acommend

Glad to see that not only I am having issues with this extremely frustrating issue. For me, it's happening when I am scrolling a bit faster and the API is not responding quickly enough. With very fast scrolling I could even reproduce the issue in the Ionic documentation.

Screenshot 2022-06-01 at 11 38 32

It's always loading 2 times and then it stops working. I also posted this issue in the Ionic forums but didn't get any response. The issue occurs when you reach the ion-infinite-scroll-content before complete() was called. What can solve it is to simply call complete() immediately before loading the data but then you have to solve the issue that the API could answer with page 3 before page 2.

derWebdesigner avatar Jun 22 '22 15:06 derWebdesigner

An additional hint after testing it quite a lot on different devices: This issue seems to only affect Android devices and browsers which could be the case because Apple has this rubberband effect that seems to help since scrolling is not stopping at a very strict position. But that's just an assumption.

derWebdesigner avatar Jun 23 '22 08:06 derWebdesigner

I know this is fairly old so apologies for bumping, but this is still an issue when testing with @ionic/react v6.1.9, are there any plans for Ionic to support better behaviour when there's an infinite list with not enough elements to initiate scrolling? I agree with the OP's expected behaviour, I'd also expect ionInfinite to fire more than once if there are more items available

Nevvulo avatar Aug 01 '22 01:08 Nevvulo

Bummed to see this is still an issue :(

fishcolin avatar Nov 04 '22 18:11 fishcolin

Bummed to see this is still an issue :(

And it's actually a really crucial one for the entire usage of the framework because infinite lists inside apps are basically everywhere. I tried to build workarounds by loading double content and caching half of it to speed up the complete() call but that resulted in other even bigger issues.

derWebdesigner avatar Nov 05 '22 11:11 derWebdesigner

This issue / unexpected behavior persists on @ionic/angular: 6.4.1.

In my case the only workaround stable enough was to fill the screen untill a scroll bar is detected, this allows the scroll event to be triggered for the infinite scroll component:

  async checkForScrollbar() {
    const scrollElement = await this.content.getScrollElement();
    return scrollElement.scrollHeight > scrollElement.clientHeight;
  }

pierrecorsini avatar Dec 15 '22 13:12 pierrecorsini

I'm facing this same issue on ionic v6.4.1 react

luantrasel avatar Jan 20 '23 17:01 luantrasel

@luantrasel I even wrote Ionic via Twitter in December because this issue feels like such a crucial one for the entire framework but never received an answer.

@pc-robelbois Yeah something like this seems to be a solution for the initial view but it still won't work when you scroll a bit faster or the server response is a bit slower for one of the requests.

derWebdesigner avatar Jan 20 '23 17:01 derWebdesigner

i solved getting the Infinite scroll whit document.getElementByID and use the complete() method specifed in ionic documentation, it marks an error in typescript but a //@ts-ignore solved it

Dan-315 avatar May 31 '23 18:05 Dan-315

It is still an issue on Ionic 7 React.

joey542 avatar Jun 09 '23 04:06 joey542

Have the same issue (Ionic 7 vue)

maelp avatar Sep 04 '23 09:09 maelp

(workaround: added a "load more" button in infinite-scroll-content to allow the user to click)

maelp avatar Sep 04 '23 09:09 maelp

Still an issue =/

My hacky solution: set "ion-infinite-scroll" height and threshold to 1px:

<ion-infinite-scroll
      (ionInfinite)="onIonInfinite($event)"
      style="height: 1px;"
      threshold="1px">
      <ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>

andrehhtm avatar Sep 20 '23 21:09 andrehhtm

@liamdebeasi Sorry to bother and link you here but this is still a huge issue for many of us and I tried the forums, Twitter, and any other possible way to get help. What ion-infinite urgently needs is a check if there is more space available without scrolling because otherwise the user can't scroll and the event is never firing, resulting in ion-infinite not working at all. This issue gets worse with bigger screens where you basically need to load lots of items immediately to make sure that you can at least fill the screen. This also is a blocker if you scroll very fast as shown above even in the Ionic documentation itself.

Thank you in advance for any help.

derWebdesigner avatar Oct 05 '23 18:10 derWebdesigner

Hey there @derWebdesigner!

So it sounds like there are two bugs reported on this thread:

  1. Scrolling down does not always trigger the infinite scroll event.
  2. If you don't have enough data to scroll then infinite scroll does not fire.

Issue 1 is definitely a bug and something I can prioritize with the team. Maybe I don't fully understand issue 2, but that seems like an implementation problem. Infinite scroll is designed to activate on scroll, so if your page does not scroll because not enough data was loaded, then infinite scroll will never activate. The solution here would be to have more data loaded initially such that the page can scroll.

liamdebeasi avatar Oct 05 '23 18:10 liamdebeasi

@liamdebeasi Thank you so much for your feedback, I appreciate it a lot. Exactly, it's basically 2 issues.

  1. This happens when the API answers too slowly which is the case as well in your documentation or for any API when you scroll very fast. If you call the complete method immediately you can fix this but then this can result in page 3 being shown before page 2 because the next API request is then fired immediately.
  2. Exactly and while "make sure to load enough data" sounds easy, it actually isn't with new phones released having bigger and bigger screens so you would need to initially load a lot more than you usually would but that makes the API response slower etc. The big issue here is: If you reach this state, there is no way for the user to load more data (because he can't scroll) or even recognize that data is missing.

derWebdesigner avatar Oct 05 '23 19:10 derWebdesigner

@liamdebeasi One additional hint: Basically both 'bugs' come from the same issue. If there is not enough space to scroll the event to load more is not fired and when you scroll too fast you reach the end of the list before complete is fired and then it's basically the same situation. It can be solved by the user though by scrolling a bit up and then down again but that's not something you would expect from a user perspective and try all the time to see if there is more.

derWebdesigner avatar Oct 05 '23 19:10 derWebdesigner

Thanks for the clarification. I'm not sure how Ionic would account for this in a general sense (i.e. for many use cases) since we don't know when your inner content is loaded. This still seems like an implementation issue. However, I can think of a few options to avoid this:

  1. Load more items than you need

As you noted above above, this isn't always feasible especially with devices that have large screens such as tablets. You would potentially be loading 50+ items at once which can have negative performance implications.

  1. Recursively paginate data

This is a slightly more sophisticated version of option 1 where you load in increments of n until your content can scroll. You could implement something like this:

a. Load n data points. b. Check if your content can scroll. (check If the height of the list > height of the content visible area) c. If your page can scroll then you are done. d. If the page cannot scroll then load n more data points and go back to step a and repeat.

  1. Add a "Load More" button at the bottom of the page

You could show this button so users can manually fetch more data. Once data has been fetched or when the infinite scroll event fires then you can hide the button.


Do any of these work for your use case?

liamdebeasi avatar Oct 05 '23 19:10 liamdebeasi

@liamdebeasi Thank you, I definitely got your point. I still think (if possible) that the component should check if it reached the "I should load the next page"-state but currently isn't because there is no actual scroll event possible or happening. Adding a very subtle "load more" button is a good idea for sure and I will probably add it now as a workaround but I am sure that others using the component are not even aware of these "restrictions" and are therefore delivering broken endless scroll pages. On iOS the first issue is a bit less affected because you have the bounce scroll and if the user pushes a bit more the event suddenly fires sometimes but that feels anything else than smooth.

So basically checking more often if the scroll area is visible and firing a scroll event based on that manually is probably the only viable option I see, isn't it?

Thank you anyway for your thoughts and efforts, I highly appreciate them and I hope you can understand my perspective that I would love to provide a smooth experience and this would help a lot if it was solved somehow.

Edit: This results in a lot more effort than I thought because if you offer the load more button or want to check all this by yourself, you also need to implement a parallel prevention that the infinite load gets fired while you are loading content yourself without it. I was searching if there is some function to at least fire the event so that I don't need to do that but couldn't find anything. So the only way is to set the infinite load to disabled as soon as I do something and enable it again afterward, right? Sorry for so much content but when I google for the issue I see lots of people having the same issue, trying to solve it somehow for over 5 years (which is when I recognized the issue the first time) and all of these approaches might look okay but they actually aren't for various reasons.

derWebdesigner avatar Oct 06 '23 09:10 derWebdesigner

Thank you, I definitely got your point. I still think (if possible) that the component should check if it reached the "I should load the next page"-state but currently isn't because there is no actual scroll event possible or happening.

Yeah that's my point: Ionic has no idea when that state is reached, only your application code does. Having Ionic check the scrollable content isn't a reliable fix because we would not be aware of any asynchronous requests that would cause the DOM to be updated and cause the scroll content check to be invalidated.

This results in a lot more effort than I thought because if you offer the load more button or want to check all this by yourself, you also need to implement a parallel prevention that the infinite load gets fired while you are loading content yourself without it. I was searching if there is some function to at least fire the event so that I don't need to do that but couldn't find anything. So the only way is to set the infinite load to disabled as soon as I do something and enable it again afterward, right?

When your loading function fires you'll want to either set disabled="true" on the button or remove it from the DOM entirely. You can also add a check to the function that returns early if another request is already in progress.


I updated the description of this issue to more accurately reflect the root problem (the event does not always fire when you scroll quickly).

liamdebeasi avatar Oct 09 '23 12:10 liamdebeasi

@liamdebeasi Thank you so much, Liam. My maybe too trivial thought was: If the ion-infinite is visible inside the viewport, then content needs to be loaded and that can be the case because it's visible right away or it gets visible when you scroll down. If somebody scrolled too fast and ion-infinite is in the viewport but complete() hasn't been called yet, you would do the check immediately afterward and both issues would be solved. But maybe you are right and these are 2 completely different use cases although they felt naturally connected to me.

Alternatively, a function to trigger ion-infinite would help to prevent the double-check to disable ion-infinite if something is loading already which basically every app has to do. I am currently testing an intersection observer directive to solve the issue in a natural way but it needs further testing.

I really like the idea you suggested of removing it from the DOM entirely or disabling it, but wouldn't that exacerbate the problem because a user is scrolling a bit faster, the component is removed from the DOM, the API is finished loading, and while I add ion-infinite to the DOM again the user already did hit the bottom of the page and the ion-infinite won't trigger again because of the same reason again.

Sorry for so much text, I hope I am not totally wrong with my thoughts, and if you think so you can always overrule me of course. Anyway thank you very much for all your efforts, I really appreciate it.

derWebdesigner avatar Oct 09 '23 13:10 derWebdesigner

I really like the idea you suggested of removing it from the DOM entirely or disabling it, but wouldn't that exacerbate the problem because a user is scrolling a bit faster, the component is removed from the DOM, the API is finished loading, and while I add ion-infinite to the DOM again the user already did hit the bottom of the page and the ion-infinite won't trigger again because of the same reason again.

To be clear, I am suggesting that you remove the ion-button (the button that lets users manually load more data) from the DOM, not ion-infinite-scroll. You can disable the ion-button when loading starts, and then remove it altogether once loading finishes (or just remove it when loading starts)

liamdebeasi avatar Oct 09 '23 15:10 liamdebeasi

@liamdebeasi Okay, I misunderstood that, thank you. Makes sense but I still have to block the ion-infinite when content needs to be loaded for other reasons like the initial load when the list isn't long enough. This is where a trigger function could maybe help. But since I am no pro when it comes to maintaining a framework it's just open feedback/thoughts I am giving and I will try to work on some of your suggested or other workarounds.

derWebdesigner avatar Oct 09 '23 15:10 derWebdesigner

Hi everyone,

Can everyone try the following dev build and let me know if it resolves the reported issue? It would be great if you could test the position="bottom" and position="top" use cases on ion-infinite-scroll. The team has done testing internally, but we'd love more testing from the community.

Dev build: 7.5.4-dev.11700602203.1e7155a1

Install Example:

npm install @ionic/[email protected] @ionic/[email protected]

Note: This dev build only resolves the bug where scrolling down quickly does not always cause ionInfinite to fire. The other issue noted in this thread was determined not to be a bug in https://github.com/ionic-team/ionic-framework/issues/18071#issuecomment-1749470660.

liamdebeasi avatar Nov 21 '23 21:11 liamdebeasi