Ionic-framework: Click delay (2s) on device but not in browser.

Created on 15 May 2017  ·  11Comments  ·  Source: ionic-team/ionic-framework

Ionic version:
[X] 2.2.0

I'm submitting a ...
[X] bug report

Current behavior:
I'm facing a click delay problem while test my app on real device (IOS/Android).
While testing on browser, clicks are very fast responsive but when I test the app on Iphone 5C or Android devices (or emulator) there is to much delay (like 2s).

You can see the difference:
ON BROWSER
ON IPHONE 5C

Expected behavior:
No click delays on device.

Steps to reproduce:
Using WKWebView with virtual list. (but without WKWebView it's the same)

Related code:
HTML:

<ion-header>
  <ion-navbar>
  <ion-buttons  end >
      <button [hidden]="!showScrollTop" ion-button style="font-size:24px;" (click)="scrollToTop()">
       <ion-icon name="arrow-up"></ion-icon>
      </button>
    </ion-buttons>
    <ion-title>
      <span>Garages</span>
    </ion-title>

  </ion-navbar>
</ion-header>





<ion-content class="feed-content">

   <ion-refresher (ionRefresh)="doRefresh($event)">
    <ion-refresher-content></ion-refresher-content>
  </ion-refresher>


    <ion-toolbar class="search-toolbar border-bottom">
    <ion-buttons start>
      <button ion-button icon-only (click)="geolocateMe()" class="geolocation-button" >
        <ion-icon name="locate"></ion-icon>
      </button>
    </ion-buttons>
    <span class="actual-pos" placeholder="" >Position actuelle: {{adresse_actuelle}}</span>
  </ion-toolbar>

  <form [formGroup]="rangeForm">
    <ion-list class="range-list">
      <ion-item class="range-item single-range">
        <ion-label>
          <h2 class="range-label">Distance maximale:</h2>
          <h3 class="range-value">{{rangeForm.controls.single.value}}km</h3>
        </ion-label>
        <ion-range formControlName="single" (ionChange)="rangeChange($event)" min="1" max="50" step="1" snaps="true" pin="false" ></ion-range>

      </ion-item>
    </ion-list>
  </form>


  <ion-col [hidden]="!rangeUpdated" no-padding width-100 class="refresh-arrow-col">
    <div  > 
      <p class="refresh-text">Tirez pour rafraichir les résultats</p>
      <ion-icon class="refresh-arrow" name="arrow-down"></ion-icon>
    </div>
  </ion-col>

  <ion-col no-padding width-100 class="refresh-arrow-col">
      <p class="refresh-text" style="padding-bottom:15px;">Garages à proximité: <strong>{{feed?.posts?.length}}</strong></p>
  </ion-col>



 <ion-list [virtualScroll]="feed.posts" approxItemHeight="100px"  no-lines>
  <div class="feed-item" *virtualItem="let post" style="width: 100%">
    <ion-card [class.premium]="post.premium==1">


      <ion-row tappable class="user-main-data-row" (click)="goToGarage(post.garage.id)" >
         <div *ngIf="post.premium==1">
           <span class="premium-text">SPONSORISÉ</span>
      </div> 
        <ion-col no-padding width-33>

          <preload-image class="user-image" [ratio]="{w:1, h:1}" src="http://garageadvisor.pre-production.ovh/upload/{{post.garage.logo}}" alt=""  ></preload-image>
        </ion-col>
        <ion-col class="center" no-padding width-67 style="padding-left:5px;">
          <p class="item-title">{{post.garage.nom}}</p>


              <p class="avis-list">Note: <span class="note">{{post.garage.moyenneAvis}}/5</span> ({{post.garage.nbAvis}} Avis) </p>

        </ion-col>
      </ion-row>

      <ion-card-content tappable (click)="goToGarage(post.garage.id)">

                 <ion-list class="details-list" no-lines>
                  <ion-item class="place-location">
                    <ion-avatar item-left>
                      <ion-icon name="pin"></ion-icon>
                    </ion-avatar>
                    <div *ngIf="post.distance<1">
                      <span class="location-text">{{post.garage.adresse}} (à {{post.distance*1000 | num }} m)</span>
                    </div> 
                    <div *ngIf="post.distance>1">
                      <span class="location-text">{{post.garage.adresse}} (à {{post.distance | num }} km)</span>
                    </div> 
                  </ion-item>
                </ion-list>
      </ion-card-content>
      <ion-row no-padding class="actions-row">
        <ion-col no-padding width-50 text-left>
          <button tappable class="action-button" ion-button clear small icon-left (click)="goToGarage(post.garage.id)">
            <ion-icon name='pricetags'></ion-icon>
            {{post.nombrePromotion}} Promotion(s)
          </button>
        </ion-col>
        <ion-col  no-padding width-45 text-right >
          <button  class="action-button" ion-button clear small icon-left (click)="navigate(post.garage.adresse)">
            <ion-icon name='navigate'></ion-icon>
            Itinéraire
          </button>
        </ion-col>

      </ion-row>
    </ion-card>
    <div style="height:12px"></div>
  </div>
  </ion-list>
</ion-content>

component.ts:

import { Component, ViewChild, ChangeDetectorRef } from '@angular/core';
import { Content } from 'ionic-angular';
import { Geolocation } from '@ionic-native/geolocation';
import { NavController, NavParams, LoadingController, AlertController, ToastController } from 'ionic-angular';
import { LaunchNavigator, LaunchNavigatorOptions } from '@ionic-native/launch-navigator';
import { FormGroup, FormControl } from '@angular/forms';
import { ProfilePage } from '../profile/profile';
import { ContactCardPage } from '../contact-card/contact-card';
import 'rxjs/Rx';
import { FeedPostModel } from './feed.model';
import { FeedModel } from './feed.model';
import { FeedService } from './feed.service';
import { SocialSharing} from '@ionic-native/social-sharing';
import { WalkthroughPage } from '../walkthrough/walkthrough';
import {App} from 'ionic-angular';
import { NativeStorage } from '@ionic-native/native-storage';


import { LoginService } from '../login/login.service';


@Component({
  selector: 'feed-page',
  templateUrl: 'feed.html'
})
export class FeedPage {
  feed: FeedModel = new FeedModel();
  rangeForm: any;
  loading: any;
  page: number;
  next: boolean;
  rangeUpdated:boolean;
  latitude:any;
  longitude:any;
  adresse_actuelle:string;
  range_distance:any;
  showScrollTop:boolean;

  constructor(
    public app: App,
    public nav: NavController,
    public feedService: FeedService,
    public navParams: NavParams,
    public loadingCtrl: LoadingController,
    public alertCtrl: AlertController,
    private launchNavigator: LaunchNavigator,
    public loginService: LoginService,
    public toastCtrl: ToastController,
    private geolocation: Geolocation,
    private socialSharing: SocialSharing,
    public nativeStorage: NativeStorage,
    private changeDetectorRef: ChangeDetectorRef

  ) {

    this.loading = this.loadingCtrl.create();

    this.page=0;

    this.next=false;

    this.rangeUpdated=false;

    if (localStorage.getItem("range_distance"))this.range_distance=localStorage.getItem("range_distance");
    else this.range_distance=25;

    this.rangeForm = new FormGroup({
      single: new FormControl(this.range_distance)
    });

    this.latitude=0; 
    this.longitude=0;
    this.adresse_actuelle=localStorage.getItem("adresse-actuelle");
    this.showScrollTop=false;


  }


  ionViewDidLoad() {

    let loading = this.loadingCtrl.create({
      content: 'Mise à jour de votre position'
    });
    loading.present();
    let posOptions = {enableHighAccuracy: true};
    this.geolocation.getCurrentPosition(posOptions).then((position) => {
          localStorage.setItem("latitude", String(position.coords.latitude));     // Ajoute la latitude
          localStorage.setItem("longitude", String(position.coords.longitude));   // Ajoute la longitude
          loading.dismiss().catch(() => {});
          let loading2 = this.loadingCtrl.create();
          loading2.present();
          this.loginService.
          getAdresse()            
          .then(data => {
                  localStorage.setItem("adresse-actuelle", data[0].long_name+" "+data[1].long_name+", "+data[2].long_name);
                  this.adresse_actuelle=data[0].long_name+" "+data[1].long_name+", "+data[2].long_name;
                  this.feedService
                  .getGarages(0, this.rangeForm.controls.single.value, 1000)
                  .then(data => {
                        this.feed.posts = data.Content as FeedPostModel[];
                        this.next = data.Next;
                        loading2.dismiss().catch(() => {});
                  }).catch(error => { 
                    loading2.dismiss().catch(() => {});
                    this.errorAlert(error);
                  });
          }).catch((error) => {
          loading2.dismiss().catch(() => {});
          this.errorAlert("GoogleMap");
          });

          loading.dismiss().catch(() => {});

    }).catch((error) => {
      loading.dismiss().catch(() => {});
      this.errorAlert("GPS");
    });



  }

  @ViewChild(Content) content: Content;

  scrollToTop() {
    this.content.scrollToTop(2000);
    this.showScrollTop=false;
  }

  ngAfterViewInit() {
    this.content.ionScrollEnd.subscribe((data)=>{
      if (data.scrollTop > 1000) {
        this.showScrollTop=true;
      } else {
        this.showScrollTop=false;
      }
      this.changeDetectorRef.detectChanges();
    });
  }

  ionViewDidEnter () {
      if (this.adresse_actuelle!=localStorage.getItem("adresse-actuelle"))this.rangeUpdated=true;
      this.adresse_actuelle=localStorage.getItem("adresse-actuelle");
  }

   doInfinite(infiniteScroll) {
      this.page++;
      this.feedService
        .getGarages(this.page, this.rangeForm.controls.single.value, 10)
        .then(data => {
          this.feed.posts = this.feed.posts.concat(data.Content as FeedPostModel[]);
          this.next = data.Next;
          infiniteScroll.complete();
        }).catch(error => { 
        this.loading.dismiss().catch(() => {});
        this.errorAlert(error);
      });
  }

  goToProfile(event, item) {
    this.nav.push(ProfilePage, {
      user: item
    });
  }

  getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }

  sharePost(post) {
   //this code is to use the social sharing plugin
   // message, subject, file, url
   this.socialSharing.share(post.description, post.title, post.image)
   .then(() => {

   })
   .catch(() => {

   });
 }


  rangeChange(range: Range) {
    this.rangeUpdated=true;  
    localStorage.setItem("range_distance", this.rangeForm.controls.single.value);

  }

  goToGarage(id_garage: any) {
      this.nav.push(ContactCardPage, { id_garage: id_garage });
  }

  doRefresh(refresher) {
    this.page=0;
    this.feedService
    .getGarages(0, this.rangeForm.controls.single.value, 1000)
    .then(data => {
      this.feed.posts = data.Content as FeedPostModel[];
      this.next = data.Next;
      this.rangeUpdated=false;
      refresher.complete();
    }).catch(error => { 
      this.loading.dismiss().catch(() => {});
      this.errorAlert(error);
    });
  }


  navigate(adresse){
    let options: LaunchNavigatorOptions = {
      //start: [parseFloat(localStorage.getItem("latitude")),parseFloat(localStorage.getItem("longitude"))],
      appSelectionDialogHeader: "Veuillez choisir l'application GPS",
      appSelectionCancelButton: "Annuler"
    };

  this.launchNavigator.navigate(adresse, options)
    .then(
      success => console.log('Launched navigator'),
      error => console.log('Error launching navigator', error)
    );


  }

  geolocateMe(){
    let loading = this.loadingCtrl.create();
    loading.present();
    let posOptions = {enableHighAccuracy: true};
    this.geolocation.getCurrentPosition(posOptions).then((position) => {
    localStorage.setItem("latitude", String(position.coords.latitude));     // Ajoute la latitude
    localStorage.setItem("longitude", String(position.coords.longitude));   // Ajoute la longitude

    this.loginService.
    getAdresse()            
    .then(data => {
      localStorage.setItem("adresse-actuelle", data[0].long_name+" "+data[1].long_name+", "+data[2].long_name);
      this.adresse_actuelle=data[0].long_name+" "+data[1].long_name+", "+data[2].long_name;
    }).catch((error) => {
    loading.dismiss().catch(() => {});
    });
    this.rangeUpdated=true;
    let toast = this.toastCtrl.create({
    message: "Votre position a bien été mise à jour.",
    duration: 3000,
    position: 'bottom'
    });
    toast.present();
    loading.dismiss().catch(() => {});

    }).catch((error) => {
      loading.dismiss().catch(() => {});
      this.errorAlert("GPS");
    });
  }



  errorAlert(error) {
    let message:string;
    if (error=="GPS"){ 
      message="Veuillez autoriser GarageAdvisor à accéder au service de localisation de votre téléphone et réessayez.";
    } else if (error=="GoogleMap") { 
      message="GoogleMap n'est pas accèssible. Veuillez réessayer ultérieurement."; 
    } else {
      if (error.status==400){
          message=error.json().Error;
      } else if (error.status==401){
          message="Votre session a expiré. Veuillez vous reconnecter."
      } else if (error.status==403){
          if (error.json().Error=="No_Enabled"){
            message="Votre compte a été suspendu. Veuillez contacter l'administrateur."
          } else  {
            message="Vous n'avez pas les droits d'accèder à cette page."
          }
      } else if (error.status==404){
          if (error.json().Error=="Not_Found_Garage"){
            message="Ce garage n'existe pas ou a été supprimé.";
          } else if (error.json().Error=="Not_Found_Promotion"){
            message="Cette promotion n'existe pas ou a été supprimée.";
          } else if (error.json().Error=="Not_Found_Member"){
            message="Votre compte n'existe pas ou a été supprimé.";
          } else if (error.json().Error=="Not_Found_Vente"){
            message="Cette vente n'existe pas ou a été supprimée.";
          } else if (error.json().Error=="Not_Found_Avis"){
            message="Cet avis n'existe pas ou a été supprimé.";
          } else message="Page introuvable.";
      } else message="Une erreur interne est survenue. Veuillez réessayer ultérierement."
    }

    let alert = this.alertCtrl.create({
      title: 'Erreur',
      subTitle: message,
      buttons: [
      {
        text: 'Ok',
        role: 'cancel',
        handler: () => {
          if ((error.status==401) || (error.status==500) || ((error.status==403) && (error.json().Error=="No_Enabled"))){
            localStorage.setItem("token","");
            localStorage.setItem("email","");
            localStorage.setItem("id","");
            this.nativeStorage.clear();
            this.app.getRootNav().setRoot(WalkthroughPage);
          }
          if ((error.status==404) || ((error.status==403) && (error.json().Error!="No_Enabled")) ){
            this.nav.pop();
          }
        }
      }
    ]
    });
    alert.present();
  }



}

Other information:
The problem persist on both platforms (iOS & Android).
There is no exceptions thrown in console.
Migrating to 3.2.0 didn't solve the problem.
I tried to only display a button in virtual scroll (without images etc), didn't solve the problem.
Uninstalling WKWebView didn't solve the problem.

Ionic info:

Cordova CLI: 6.5.0 
Ionic Framework Version: 2.2.0
Ionic CLI Version: 2.2.1
Ionic App Lib Version: 2.2.0
Ionic App Scripts Version: 1.1.4
ios-deploy version: Not installed
ios-sim version: 5.0.6 
OS: macOS Sierra
Node Version: v7.7.3
Xcode version: Xcode 8.3.2 Build version 8E2002

Most helpful comment

@evenmind The delay is expected for the components that are not clickable. In your code above, <ion-item> is not a clickable component hence the delay. Try doing it this way:

html <ion-list> <button ion-item (click)="doOne()">Item1</button> <button ion-item (click)="doTwo()">Item2</button> <button ion-item (click)="doThree()">Item3</button> </ion-list>
to avoid the delay.
Refer to this click-delays for more information

All 11 comments

Hello, thanks for using Ionic. Could you post a repo we could use to reproduce this issue? Also, when testing on a device are you running ionic run android --prod?

Yes i ionic build ios --prod and then install it via Xcode. If I post a repo via Plunker you will not be able to run it on divice right? Here is the IonicView id: C676D0D8
I could share the project git repo with you in private.

So jgw96 can I share the git repo with you in private? Can you please help me with this issue?
I will donnate to you or ionic theme if you can help me.. Thank you

@evenmind The delay is expected for the components that are not clickable. In your code above, <ion-item> is not a clickable component hence the delay. Try doing it this way:

html <ion-list> <button ion-item (click)="doOne()">Item1</button> <button ion-item (click)="doTwo()">Item2</button> <button ion-item (click)="doThree()">Item3</button> </ion-list>
to avoid the delay.
Refer to this click-delays for more information

@chandanch Ok but on the video I click on this:

          <button tappable class="action-button" ion-button clear small icon-left (click)="goToGarage(post.garage.id)">
            <ion-icon name='pricetags'></ion-icon>
            {{post.nombrePromotion}} Promotion(s)
          </button>

So I think this is not the problem because the delay is still here when I click on the button..

how many items do you have in the page?
are you using flex?

I have 10 to 500 items. Performances are pretty the same cause i use virtual scroll. And yes i display ioncards in flex! It could be the problem??

I tested it with a more simple template:

<ion-content class="feed-content">

  <ion-col no-padding width-100 class="refresh-arrow-col">
      <p class="refresh-text" style="padding-bottom:15px;">Garages à proximité: <strong>{{feed?.posts?.length}}</strong></p>
  </ion-col>

 <ion-list [virtualScroll]="feed.posts" approxItemHeight="50px"  no-lines>
  <div class="feed-item" *virtualItem="let post" style="width: 100%">
    <ion-card [class.premium]="post.premium==1">

      <ion-row no-padding class="actions-row" style="display:block">
        <ion-col no-padding width-50 text-left>
          <button class="action-button" ion-button clear small icon-left (click)="goToGarage(post.garage.id)">
            <ion-icon name='pricetags'></ion-icon>
            {{post.nombrePromotion}} Promotion(s)
          </button>
        </ion-col>
      </ion-row>

    </ion-card>
    <div style="height:12px"></div>
  </div>
  </ion-list>

</ion-content>

With no shadows, no display:flex and only buttons in the list and there is result:
WATCH VIDEO

The click delay problem is still there.. It's inusable on iphone / android. It's very annoying.

Have you another ideas? Can I share my project with some one? Thank you

@manucorporat
Any ideas for the workaround?

Hello all! I am going to close this issue as a duplicate of https://github.com/ionic-team/ionic/issues/11489. Thanks for using Ionic!

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

Was this page helpful?
0 / 5 - 0 ratings