5

I want to hide or show elements when there is or there isn't a scrollbar on ion-content. More specifically, I want to show a button (to load more items in a list) when there's no scrollbar and hide it where there is a scrollbar (so the loading of more items is done by ion-infinite-scroll).

My Ionic app will also be deployed to the desktop so users with large screens won't initially see a scrollbar and thus ion-infinite-scroll won't be triggered.

Here's a demo that showcases the issue:

home.page.html

<ion-header>
  <ion-toolbar>
    <ion-title>
      Ionic header
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div class="ion-padding">
    <p *ngFor="let item of itemList">{{ item }}</p>

    <!-- How to hide this button when ion-content has a scrollbar? -->
    <!-- *ngIf="???" -->
    <ion-button (click)="incrementItemList(5)">Load more items</ion-button>
  </div>

  <ion-infinite-scroll (ionInfinite)="loadMoreItems($event)">
    <ion-infinite-scroll-content loadingSpinner="crescent"></ion-infinite-scroll-content>
  </ion-infinite-scroll>
</ion-content>

<ion-footer>
  <ion-toolbar>
    <ion-title>
      Ionic footer
    </ion-title>
  </ion-toolbar>
</ion-footer>

home.page.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

  itemList: string[] = [];

  constructor() {}

  ionViewWillEnter() {
    this.incrementItemList(5);
  }

  incrementItemList(times: number) {
    for (let i = 1; i <= times; i++) {
      this.itemList.push(`Lorem ipsum dolor sit amet consectetur adipisicing elit. Quia placeat nam sapiente iusto eligendi`);
    }
  }

  loadMoreItems(event: any) {
    setTimeout(() => {
      this.incrementItemList(15);
      event.target.complete();
    }, 1000);
  }

}

I'm using Ionic 4.5.0 + Angular.

I have tried using getScrollElement, scrollHeight, clientHeight, offsetHeight, but with no success.

Any ideas?

nunoarruda
  • 2,679
  • 5
  • 26
  • 50
  • Can you set one flag which will be false initially and you will make it true when you call the loadMore method? Yes if it works then you also need to again reverse the flag to false after you load the additional data. – Sunny Parekh Jul 04 '19 at 09:56
  • @SunnyParekh That's not a failproof solution because screen size varies from user to user and scrollbar may not appear after the first call of the loadMore method because there might not be enough content to trigger a scrollbar. Thanks anyway for trying to help. – nunoarruda Jul 04 '19 at 12:45

3 Answers3

4

Update: I slept on it and then realised I was approaching it all wrong.

Here is a working example. It checks in three places to make sure it doesn't miss the scrollbar appearing:

  1. When the page loads
  2. When new content is added
  3. When the user scrolls

On my test environment, click the add button twice was coming in at EXACTLY the height of the screen so the button didn't always disappear. I added the ionScrollEnd check to provide an extra way of catching it.

You can remove the console.log's scattered around the code, I left it in to help you understand what was happening.

Example page:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Scrollbar Test
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [scrollEvents]="true" (ionScrollEnd)="onScrollEnd()">
  <div class="ion-padding">
    <p *ngFor="let item of itemList">{{ item }}</p>

    <ion-button *ngIf="!hasScrollbar" (click)="addMoreItemsButtonClick(5)">Load more items</ion-button>
  </div>

  <ion-infinite-scroll (ionInfinite)="loadMoreItems($event)">
    <ion-infinite-scroll-content loadingSpinner="crescent"></ion-infinite-scroll-content>
  </ion-infinite-scroll>
</ion-content>

<ion-footer>
  <ion-toolbar>
    <ion-title>
      Ionic footer
    </ion-title>
  </ion-toolbar>
</ion-footer>

Example code:

import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { IonContent } from '@ionic/angular';

@Component({
  selector: 'app-scrollheight',
  templateUrl: './scrollheight.page.html',
  styleUrls: ['./scrollheight.page.scss'],
})
export class ScrollheightPage implements OnInit {

  public hasScrollbar: Boolean = false;

  @ViewChild(IonContent) private content: IonContent;

  itemList: string[] = [];

  constructor() { }

  ngOnInit() {
    // check at startup
    this.checkForScrollbar();
  }

  ionViewWillEnter() {
    this.incrementItemList(5);
  }

  addMoreItemsButtonClick(quantity: number) {
    this.incrementItemList(quantity);

    // check after pushing to the list
    this.checkForScrollbar();
  }

  onScrollEnd() {
    // check after scrolling
    this.checkForScrollbar();
  }

  incrementItemList(times: number) {
    for (let i = 1; i <= times; i++) {
      this.itemList.push(`Lorem ipsum dolor sit amet consectetur adipisicing elit. Quia placeat nam sapiente iusto eligendi`);
    }
  }

  loadMoreItems(event: any) {
    setTimeout(() => {
      this.incrementItemList(15);

      event.target.complete();
    }, 1000);
  }

  checkForScrollbar() {
    this.content.getScrollElement().then((scrollElement) => {
      console.log("checking for scroll bar");
      console.log({scrollElement});
      console.log({scrollHeight: scrollElement.scrollHeight});
      console.log({clientHeight: scrollElement.clientHeight});
      this.hasScrollbar = (scrollElement.scrollHeight > scrollElement.clientHeight);
      console.log({hasScrollBar: this.hasScrollbar});
    });
  }
}
Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
rtpHarry
  • 13,019
  • 4
  • 43
  • 64
  • I have replaced my answer now with a working version. – rtpHarry Jul 06 '19 at 03:59
  • @nunoarruda did this solve the issue? I was really pleased when I finally figured this out. Excited to know if it worked for you. The first days coding I was actually stressed when I went to lunch because I couldn't figure it out, haha. – rtpHarry Jul 11 '19 at 06:01
  • 1
    Not exactly but it helped me reach a proper solution for my use case. Thanks a lot. – nunoarruda Oct 27 '19 at 13:51
2

With the help of rtpHarry's post (thanks!), I finally came up with a proper solution for this use case:

home.page.html

<ion-content>
  <div class="ion-padding">
    <p *ngFor="let item of itemList">{{ item }}</p>

    <!--
      '*ngIf' removes the button from the DOM and changes the size of 'ion-content' which
      is problematic in some scenarios so I toggle the visibility CSS property instead
    -->
    <ion-button [style.visibility]="hasScrollbar ? 'hidden' : 'visible'" (click)="incrementItemList(5)">Load more items</ion-button>
  </div>

  <ion-infinite-scroll (ionInfinite)="loadMoreItems($event)">
    <ion-infinite-scroll-content loadingSpinner="crescent"></ion-infinite-scroll-content>
  </ion-infinite-scroll>
</ion-content>

home.page.ts

import { Component, ViewChild, HostListener } from '@angular/core';
import { IonContent } from '@ionic/angular';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

  hasScrollbar = false;

  itemList: string[] = [];

  @ViewChild(IonContent, {static: false}) private content: IonContent;

  // checks if there's a scrollbar when the user resizes the window or zooms in/out
  @HostListener('window:resize', ['$event'])
  onResize() {
    this.checkForScrollbar();
  }

  constructor() {}

  ionViewWillEnter() {
    this.incrementItemList(5);
  }

  incrementItemList(times: number) {
    for (let i = 1; i <= times; i++) {
      this.itemList.push(`Lorem ipsum dolor sit amet consectetur adipisicing elit. Quia placeat nam sapiente iusto eligendi`);
    }

    this.checkForScrollbar();
  }

  loadMoreItems(event: any) {
    setTimeout(() => {
      this.incrementItemList(15);
      event.target.complete();
    }, 1000);
  }

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

}
nunoarruda
  • 2,679
  • 5
  • 26
  • 50
0

Can you please use "NgZone" by importing from @angular/core Example code here.

.ts

import { Component, OnInit, ViewChild, NgZone } from '@angular/core';
@Component({
  selector: 'app-selector',
  templateUrl: './app.page.html',
  styleUrls: ['./app.page.scss'],
})
export class App implements OnInit {
@ViewChild("listingContent") listingContent;
public hasScrollbar: Boolean = false;
public itemList:Array<any> = [];
    constructor(private zone: NgZone) {}

    scrollHandler(event) {
        this.zone.run(()=>{
            if (event.detail.scrollTop > (document.documentElement.clientHeight + 1)) {
                this.hasScrollbar = true;
            } else {
                this.hasScrollbar = false;
            }
        });
    }
}

.html

<ion-header no-border></ion-header>
<ion-content #listingContent padding-top (ionScroll)="scrollHandler($event)" scroll-events="true">
<div class="ion-padding">
    <p *ngFor="let item of itemList">{{ item }}</p>

    <ion-button *ngIf="!hasScrollbar" (click)="addMoreItemsButtonClick(5)">Load more items</ion-button>
  </div>

</ion-content>

Hope this will help..

Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62