Ionic-framework: bug(infinite-scroll): throws uncatchable error if scrollToTop is called before it is resolved

Created on 10 Jul 2017  路  33Comments  路  Source: ionic-team/ionic-framework

Ionic version: (check one with "x")
[ ] 1.x (For Ionic 1.x issues, please use https://github.com/ionic-team/ionic-v1)
[ ] 2.x
[x] 3.x

I'm submitting a ... (check one with "x")
[x] bug report
[ ] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/

Current behavior:
Having infinite scroll, calling content.scrollToTop() before last content.scrollToTop() resolves, throws an un-catchable error, and breaks the app.

ERROR TypeError: Cannot read property 'timeStamp' of null
at InfiniteScroll._onScroll (infinite-scroll.ts:233)
at SafeSubscriber.schedulerFn [as _next] (event_emitter.ts:121)
at SafeSubscriber.__tryOrUnsub (Subscriber.ts:254)
at SafeSubscriber.next (Subscriber.ts:204)
at Subscriber._next (Subscriber.ts:135)
at Subscriber.next (Subscriber.ts:95)
at EventEmitterProxy.Subject.next (Subject.ts:61)
at EventEmitterProxy.EventEmitter.emit (event_emitter.ts:80)
at ScrollView.scroll.onScroll (content.ts:437)
at ScrollView.setScrolling (scroll-view.ts:74)

Expected behavior:
Either be able to catch the error, or better yet don't error.

Steps to reproduce:
Plunker: http://plnkr.co/edit/ptdHfTMhBxwvAOOUvizQ?p=preview
Steps:

  • Scroll to 500 or so
  • Tap scrollToTop twice fast
  • Look at console. If no error, try again.

You can do it only once though (per refresh), because it kills the thread.

Ionic info: (run ionic info from a terminal/cmd prompt and paste output below):

global packages:

    @ionic/cli-utils : 1.4.0
    Ionic CLI        : 3.4.0

local packages:

    @ionic/app-scripts              : 2.0.0
    @ionic/cli-plugin-ionic-angular : 1.3.1
    Ionic Framework                 : ionic-angular 3.5.0

System:

    Node       : v8.1.2
    OS         : Windows 10
    Xcode      : not installed
    ios-deploy : not installed
    ios-sim    : not installed
    npm        : 5.0.3 
v3

Most helpful comment

Hi @youssmak, open file ../project/node_modules/ionic-angular/components/infinite-scroll/infinite-scroll.js then replace this code

        if (this._lastCheck + 32 > ev.timeStamp) {
            // no need to check less than every XXms
            return 2;
        }
        this._lastCheck = ev.timeStamp;

with this

        try {
            if (this._lastCheck + 32 > ev.timeStamp) {
                // no need to check less than every XXms
                return 2;
            }
            this._lastCheck = ev.timeStamp;
        } catch (e) {
            // ev is undefined
            return 2;
        }

_Note: you have to start app again ionic serve, ionic-app-script wont build on node_modules changes_

All 33 comments

Thanks for using Ionic! We will look into this.

Hey there! Does not exactly solve that issue, but setting a 500ms timeout before content.scrollToTop() helped.

same here . Cannot resolve the issue yet. That was key feature of my app.No solution yet

For anyone else stumbling on this thread looking for a quick fix, I was able to work around this, at least on iOS (will explain in a bit), by throttling calls to content.scrollToTop so they are never called where the scrolling overlaps (see Plunker). Here's the gist of the fix (requires lodash):

import { throttle } from 'lodash';

export class MyComponent {
  @ViewChild(Content) content: Content;

  constructor() {
    this.scrollToTop = throttle(this.scrollToTop, 500, { leading: true, trailing: false })
  }

  scrollToTop() {
    this.content.scrollToTop(200)
  }
}

Issues with other platforms (at least in browser)

While that fixed the issue in iOS, I noticed that in my browser (and maybe also in other platforms?), this issue also occurs if content.scrollToTop() is called while _any_ scrolling is being performed. Eg. the error occurs if the user swipes down hard to initiate a long inertia scroll and then clicks a button that triggers a scroll to top.

I'm not sure how to fix that, since I haven't been able to find a way to reliably stop scrolling of the scroll-content. For now, I'm just adding a delay if the content is scrolling to wait for it to hopefully stop scrolling:

// ... rest of file same as other snippet

scrollToTop() {
  // if scrolling, we will wait a bit before scrolling to top so any current scrolling momentum dies down
  // this.content.isScrolling always returns false on iOS for some reason
  const wait = this.content.isScrolling ? 150 : 0

  setTimeout(() => this.content.scrollToTop(200), wait)
}

This seems to eliminate the errors I was getting for my particular app based on my preliminary testing. Looking forward to a fix in Ionic.

EDIT/UPDATE: I ended up just downgrading Ionic back to 3.1.1 (from 3.4.1) and all is working again. I was noticing several new issues with scrolling that I couldn't find fixes for, so I'll just wait for a proper fix. Specifically, an issue with infinite scroll having empty space below it and not always activating when scrolled to, and the above issues regarding scrollToTop. Even with my workaround, I noticed that on iOS with Ionic 3.4.1, if the view was scrolling at all, scrollToTop wasn't doing anything.

Same problem here, any update?
I've surrounded this.content.scrollToBottom by try and catch, it seems helping.

Nope :(

+1

@jgw96 any update on this?

I need to update Ionic to get some of the iOS 11 fixes (specifically for iPhone X) and haven't been able to figure out a way around this. Thanks for any help!

Updated the Plunkr to demonstrate that even when surrounding this.content.scrollToTop with try/catch, infinite scroll stops working after the error is thrown: http://plnkr.co/edit/DiLYnRvLM2q8TW5DN9dJ?p=preview.

To recreate: scroll down a way, then either click "scrollToTop()" while there is still scroll momentum, or just double click "scrollToTop()". This will throw the error. Now, scroll to the bottom of list, and infinite scroll is not triggered.

I've verified that this is still occurring in Ionic 3.8.0.

I can confirm that the try/catch doesn't catch the error in Ionic 3.9.2 either, as it's thrown inside the InfiniteScroll class.

I have temporarily switched to jQuery for scroll top until this is fixed. Here's my code

// scroll top class
import { Content, Platform } from 'ionic-angular';
import { throttle } from 'lodash';
import * as $ from 'jquery';

export class ScrollTopHandler {
  private scrolling: boolean = false;
  private scrollEl: JQuery;

  constructor(private content: Content, private platform: Platform) {
    // optional, to prevent scroll top triggered too many times
    this.scrollTop = throttle(this.scrollTop, 500, { leading: true, trailing: false });
  }

  scrollTop() {
    if (!this.scrollEl) {
      this.scrollEl = $(this.content.getScrollElement());
    }

    // scroll top in progress
    if (this.scrolling) {
      return;
    }

    // page has not been scrolled down
    if (this.scrollEl.scrollTop() === 0) {
      return;
    }

    this.scrolling = true;

    // need to stop scroll momentum on ios before scroll top
    if (this.platform.is('ios')) {
      // disable and stop scroll momentum
      this.scrollEl.css('-webkit-overflow-scrolling', 'auto');
      this.scrollEl.css('overflow', 'hidden');

      setTimeout(() => {
        // re-enable scroll momentum
        this.scrollEl.css('-webkit-overflow-scrolling', 'touch');
        this.scrollEl.css('overflow', '');

        this.triggerScrollTop();

      }, 50);

    } else {
      this.triggerScrollTop();
    }
  }

  private triggerScrollTop() {
    this.scrollEl.animate({ scrollTop: 0 }, 300, () => { this.scrolling = false; });

    // use this method may trigger ev.timeStamps error...
    // this.content
    //   .scrollToTop(300)
    //   .then(() => {
    //     this.scrolling = false;
    //   });
  }
}
// example usage
import { Component, ViewChild, OnInit } from '@angular/core';
import { IonicPage, Platform } from 'ionic-angular';
import { ScrollTopHandler } from './scrollTopHandler';

@IonicPage({ name: 'home' })
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage implements OnInit {
  @ViewChild(Content) content: Content;

  constructor(private platform: Platform) { }

  ngOnInit() {
    this.scrollTopHandler = new ScrollTopHandler(this.content, this.platform);
  }

  triggerScrollTop() {
    this.scrollTopHandler.scrollTop();
  }
}

Thank you so much for sharing your workaround, @laitedliang. I just incorporated it into my code, and it is getting me around the issue for now.

annoying issue :/

here is my temporary fix (works partially : only if scrollToTop is tapped multiple times and not if content is scrolling)

scrollToTop(): void {
    if (!this.scrollingTop) {
      this.scrollingTop = true;
      try {
        this.ionContent.scrollToTop()
          .then(res => this.scrollingTop = false)
          .catch(err => this.logService.warn(err)); // show log in dev env
      } catch (e) {
        this.scrollingTop = false
        this.logService.warn(e); // show log in dev env
      }
    }
  }

Any update?

I don鈥檛 think this will get fixed soon, as the holidays are here and the team is focused on getting Ionic v4 ready.

I PR'd a fix that I've been using at work for the past month, never seen that error since

@AmitMY could you please merge the PR #13719 ?

@youssmak I don't have merge privileges, and master is kind of frozen until v4... so no, sorry... I want it merged too :)

Hi @youssmak, open file ../project/node_modules/ionic-angular/components/infinite-scroll/infinite-scroll.js then replace this code

        if (this._lastCheck + 32 > ev.timeStamp) {
            // no need to check less than every XXms
            return 2;
        }
        this._lastCheck = ev.timeStamp;

with this

        try {
            if (this._lastCheck + 32 > ev.timeStamp) {
                // no need to check less than every XXms
                return 2;
            }
            this._lastCheck = ev.timeStamp;
        } catch (e) {
            // ev is undefined
            return 2;
        }

_Note: you have to start app again ionic serve, ionic-app-script wont build on node_modules changes_

thks @sfaizanh but it's a bad habit to edit node_modules files directly.
The next time you will update your dependencies you will lost all of your changes.
You could do it only if you want to test some changes and you don't have the time to fork the repo.

The workaround works like charm. Thank you @sfaizanh

@AmitMY Are there any plans to fix this issue?

@jordins Not that I know of. This might not be a problem in v4

@jordins You can try @laitedliang fix https://github.com/ionic-team/ionic/issues/12309#issuecomment-350427865 It's working well on 3.9.2, it does require JQuery though. As others pointed out, it may take time to have a real official fix.

As stated here -> https://github.com/ionic-team/ionic/pull/12602#issuecomment-372471002 by the Ionitron, this won't be fixed due to V4, so I think it's _safe_ to edit the ionic-angular node modules source as adviced in https://github.com/ionic-team/ionic/issues/12309#issuecomment-355118279 because the bug does not appear (CONFIRM NEEDED) in V4, so the update does not break things

@sfaizanh his workaround fix my problem, but Im worried that I will forget to update inifite-scroll.js file after a long time

I hope this is fixed with the next version. Looks like I'm adding this file to my .gitignore for a while, thanks @sfaizanh!

@sfaizanh his workaround fix my problem, thx

I was able to workaround this error without editing node_modules by only inserting the ion-infinite-scroll after the initial load and scroll to bottom is complete.

```


I did still see some strange behaviour - the spinner continued in some cases, even though logging showed complete() was being called.  To work around that, I call complete after 1 second, regardless.

 ```
doInfinite(infiniteScroll) {
    setTimeout(infiniteScroll.complete, 1000); //Sometimes spinner stays even though below should catch all cases...

    if (this.thread.results.length >= this.thread.totalCount) {
      infiniteScroll.complete();
      return;
    }
    this.loadData(function () {
      infiniteScroll.complete();
    });
  }
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({...})
export class MyPage{
  scrollToTop() {
    this.navCtrl.setRoot('MyPage'); //so you will go to the top of page.
  }
}
<ion-tabs [tabsPlacement]="tabsPlacement" [tabsLayout]="tabsLayout" #tabs>
    <ion-tab (ionSelect)="tabSelected($event)"

    tabSelected() {
        var content = document.querySelector('.show-tab .scroll-content');
        content.scrollTop = 0; 
    }

Thanks for the issue! We have moved the source code and issues for Ionic 3 into a separate repository. I am moving this issue to the repository for Ionic 3. Please track this issue over there.

Thank you for using Ionic!

Was this page helpful?
0 / 5 - 0 ratings