How to detect scroll direction in ionic 4 - ionic-framework

I want to hide and show my tab bar on scroll. Wanna hide it when user scrolls down and show it when they scroll up. How to determine the direction in the onScroll method?
Here below my onScroll function:
onScroll($event: CustomEvent<ScrollDetail>) {
if ($event && $event.detail && $event.detail.scrollTop) {
let scrollTop = $event.detail.scrollTop;
console.log($event, scrollTop);
document.querySelector('ion-tab-bar').style.display = 'none';
}
}

I think you just need to enable scroll events (Ionic 4 requires it) on ion-content element and bind ionScroll event to your method + bind your tab visibility to a variable (I used footer for this example):
<ion-content padding [scrollEvents]="true" (ionScroll)="onScroll($event)">
<ion-list>
<ion-item *ngFor="let item of [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]">
I am item # {{ item }}
</ion-item>
</ion-list>
</ion-content>
<ion-footer [hidden]="footerHidden">
<ion-toolbar>Hi, I am footer, I hide on scroll down, I am revealed on scroll up</ion-toolbar>
</ion-footer>
Now your ts file can be something like this:
///
footerHidden: boolean;
constructor(
) {}
onScroll(event) {
// used a couple of "guards" to prevent unnecessary assignments if scrolling in a direction and the var is set already:
if (event.detail.deltaY > 0 && this.footerHidden) return;
if (event.detail.deltaY < 0 && !this.footerHidden) return;
if (event.detail.deltaY > 0) {
console.log("scrolling down, hiding footer...");
this.footerHidden = true;
} else {
console.log("scrolling up, revealing footer...");
this.footerHidden = false;
};
};
///
See demo here:
https://stackblitz.com/edit/ionic-v4-euwnrg
UPDATE FOR TABS:
create new tabs app with ionic (ionic start myTabsApp, then chose tabs starter)
Then create shared service (ionic g, then generate a service) so that your tabs hidden flag boolean was available across many pages as needed:
import { Injectable } from '#angular/core';
#Injectable({ providedIn: 'root' }) export class FoundationService {
public hiddenTabs: boolean;
constructor() { }
}
Then import foundation service into your tab1 and do changes to your template:
<ion-header>
<ion-toolbar>
<ion-title>
Tab One
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding [scrollEvents]="true" (ionScroll)="onScroll($event)">
<ion-list>
<ion-item *ngFor="let item of [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]">
I am item # {{ item }}
</ion-item>
</ion-list>
</ion-content>
Then update your tab1 ts:
import { Component } from '#angular/core'; import { FoundationService } from '../services/foundation.service';
#Component({ selector: 'app-tab1', templateUrl: 'tab1.page.html', styleUrls: ['tab1.page.scss'] }) export class Tab1Page {
constructor(private foundation: FoundationService) {}
onScroll(event) {
// used a couple of "guards" to prevent unnecessary assignments if scrolling in a direction and the var is set already:
if (event.detail.deltaY > 0 && this.foundation.hiddenTabs) return;
if (event.detail.deltaY < 0 && !this.foundation.hiddenTabs) return;
if (event.detail.deltaY > 0) {
console.log("scrolling down, hiding footer...");
this.foundation.hiddenTabs = true;
} else {
console.log("scrolling up, revealing footer...");
this.foundation.hiddenTabs = false;
}; };
}
Now update tabs.ts to import foundation service:
import { Component } from '#angular/core';
import { FoundationService } from '../services/foundation.service';
#Component({
selector: 'app-tabs',
templateUrl: 'tabs.page.html',
styleUrls: ['tabs.page.scss']
})
export class TabsPage {
constructor(private foundation: FoundationService) {
}
}
And also update template of tabs page to bind the boolean:
<ion-tabs>
<ion-tab-bar slot="bottom" [hidden]="foundation.hiddenTabs">
<ion-tab-button tab="tab1">
<ion-icon name="flash"></ion-icon>
<ion-label>Tab One</ion-label>
</ion-tab-button>
<ion-tab-button tab="tab2">
<ion-icon name="apps"></ion-icon>
<ion-label>Tab Two</ion-label>
</ion-tab-button>
<ion-tab-button tab="tab3">
<ion-icon name="send"></ion-icon>
<ion-label>Tab Three</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
Then run ionic serve and try it out.
The method I wrote is rather raw and naive so you way want to update / tune logic of what changes in the scrolling event should call for boolean to be false /true;

It's really buggy to use deltaY, here is slightly different approach, but more reliable.
Define variable and store lastScrollTop value, then compare currentScrollTop with lastScrollTop
In this case currentScrollTop = event.detail.scrollTop
HTML
<ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
<!-- Your Structure here -->
</ion-content>
Javascript (v1)
public lastScrollTop = 0;
public isHidden = false;
public handleScroll(event): void {
this.isHidden = this.lastScrollTop < event.detail.scrollTop;
this.lastScrollTop = event.detail.scrollTop;
}
Javascript (v2)
public lastScrollTop = 0;
public handleScroll(event): void {
if(this.lastScrollTop < event.detail.scrollTop) {
console.log("scrolling down")
} else {
console.log("scrolling up")
}
this.lastScrollTop = event.detail.scrollTop;
}

Related

Ionic 4 pass property to custom component

I've written a custom component to handle my ion-header as all I need to change per page is the title. However, the title is not displaying as the component doesn't seem to be getting the property from the app page.
Component Template
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title> {{title}} </ion-title>
Component Typescript
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-custom-toolbar',
templateUrl: './custom-toolbar.component.html',
styleUrls: ['./custom-toolbar.component.scss'],
})
export class CustomToolbarComponent implements OnInit {
#Input() title: any;
constructor() { }
ngOnInit() {
}
}
App Page Template
<app-custom-toolbar [title]="Dashboard"></app-custom-toolbar>
You need to declare #Input() title in your custom component to pass value from parent into it like -
Child Component
...
...
#Input() title: any;
...
...
Parent Component HTML -
<child-component [title]="somePropertyInParentComponent"></child-component>
Edit: According to your updated question . Try removing [] from the title property in parent component
As Pawan Sharma says, you need to declare an #Input,
In this link you can find more information about it.
https://victorroblesweb.es/2016/11/07/input-output-angular-2/
Typescript
import { Component, OnInit, Input } from '#angular/core';
import { NavController } from '#ionic/angular';
#Component({
selector: 'app-footertoolbar',
templateUrl: './footertoolbar.page.html',
})
export class FooterToolbarPage implements OnInit {
#Input() public activeIndex:number;
constructor(private navCtrl: NavController) { }
ngOnInit() {}
public GoToPage() { console.log(this.activeIndex); }
}
HTML
<ion-toolbar color="dark">
<ion-buttons class="sidemargin" slot="secondary">
<ion-button (click)="GoToPage()"></ion-button>
</ion-buttons>
</ion-toolbar>
HTML of the component that use the component
<app-footertoolbar [activeIndex]="0" >
</app-footertoolbar>
Reposting my comment as requested:
I think if you have the [] around it then to pass a string back you would need "'dashboard'" (so a " with a ' inside it).

ionic back button does not change text dynamically

After setting back button text using config,
it does not reflect right away in nav bar.
Have to pop and push the page again.
playground:
https://stackblitz.com/edit/backbuttonbug.
you can see in contact page,
setting back button text does not reflect in self page and even in other nav stack
code:
previous page:
export class AboutPage {
constructor(public navCtrl: NavController) {}
contact() {
this.navCtrl.push(ContactPage);
}
}
Next page:
export class ContactPage {
constructor(public navCtrl: NavController,
public config: Config) {}
toChinese() {
this.config.set("backButtonText", '返回');
}
toEnglish() {
this.config.set("backButtonText", 'back');
}
}
<ion-header>
<ion-navbar>
<ion-title>
Contact
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<button ion-button (tap)="toChinese()">toChinese</button>
<button ion-button (tap)="toEnglish()">toEnglish</button>
</ion-content>
I suspect this is a bug and have opened a issue:
https://github.com/ionic-team/ionic-v3/issues/976.
and find another issue similar:
https://github.com/ionic-team/ionic/issues/7043
is that a ionic bug / my program bug?
hope to see advice
You haven't added any code so I'm not 100% sure of what you've tried already but try this:
import { ViewController } from 'ionic-angular';
...
ionViewDidEnter() {
this.viewCtrl.setBackButtonText('Some dynamic button text');
}
Edit
Sorry didn't see your Stackblitz example, this works:
import { Component } from '#angular/core';
import { NavController, Config, ViewController } from 'ionic-angular';
#Component({
selector: 'page-contact',
templateUrl: 'contact.html'
})
export class ContactPage {
constructor(public navCtrl: NavController,
public config: Config,
private viewCtrl: ViewController) {
}
toChinese() {
this.viewCtrl.setBackButtonText('返回');
}
toEnglish() {
this.viewCtrl.setBackButtonText('Back');
}
}

Close Modal in Ionic 4 by Back Button

I have a Modal in Ionic 4. I'd like to close it, when a user press the back button on her mobile (or the back button in her browser).
Does anyone know how I can do this?
EDIT: More details:
I have a button that opens my modal:
async onClick() {
const modal = await this.modalController.create({
component: Foo,
});
return await modal.present();
}
Component Foo doesn't have much more content than a button that closes the modal: this.modalController.dismiss();. So far so good.
On my mobile, however, the app now closes when the modal is open and the user taps the mobile's back button. But in this case only the modal should close.
Enol's answer helped me find a solution, thanks for that.
platform.registerBackButtonAction does no longer exist in v4. I tried platform.backButton.subscribe instead, but it didn't work. What works is this:
private backbuttonSubscription: Subscription;
constructor(private modalCtrl: ModalController) {
ngOnInit() {
const event = fromEvent(document, 'backbutton');
this.backbuttonSubscription = event.subscribe(async () => {
const modal = await this.modalCtrl.getTop();
if (modal) {
modal.dismiss();
}
});
}
ngOnDestroy() {
this.backbuttonSubscription.unsubscribe();
}
You can use the registerBackButtonAction method that Platform service contains. This method allows override the default native action of the hardware back button. The method accepts a callback function as parameter where you can implement your logic. In summary you should do the following:
Inject the Platform service inside the Foo component.
Call the registerBackButtonAction in the ngOnInit (or another init method) and pass a function callback as parameter that executes the logic to close the modal (this.modalController.dismiss();)
Clear the action when the modal component is closed (for example in ngOnDestroy method). To do that, the registerBackButtonAction returns a function that when is called the action is removed.
The code should be something like:
constructor(private platform: Platform) {
...
}
ngOnInit() {
this.unregisterBackAction = this.platform.registerBackButtonAction(() => {
this.modalController.dismiss();
})
}
ngOnDestroy() {
if(this.unregisterBackAction) this.unregisterBackAction();
}
For ionic 5 user
this.platform.backButton.subscribeWithPriority(999, async() => {
if (this.modalCtrl.getTop()) {
const modal = await this.modalCtrl.getTop();
console.log(modal)
if (modal) {
this.modalCtrl.dismiss();
return;
} else {
if (this.router.url=="/myrootpage" ) {
navigator['app'].exitApp();
} else {
this.navCtrl.pop();
}
}
} else {
if (this.router.url=="/myrootpage") {
navigator['app'].exitApp();
} else {
this.navCtrl.pop();
}
}
});
Yes, are almost on the way....
you just need to change in HTML part. I did in this way.
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button color="dark" (click)="closeModal()">
<ion-icon name="arrow-back"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Create Pin</ion-title>
</ion-toolbar>
</ion-header>
after this, you just need to create a function that will close your modal popup.
in your ts file
closeModal() {
this.modalCtrl.dismiss();
}
I hope that will help you.
Based on the initial answer by Markus, You can decide to; Instead of unsubscribing after each back button event. You may want to listen to back-button events globally in your application and only call exit on specific pages.
import { fromEvent } from "rxjs"; // import fromEvent from rxjs
import { Router } from "#angular/router"; // import angular router as well
import { Location } from "#angular/common"; // import location from angular common
constructor(private router: Router, private location: Location) {
// Call the function when the app initializes at app.component.ts. it will watch for
// back button events globally in the application.
this.backButtonEvent();
}
// Function to present the exit alert
async exitAlert() {
const alert = await this.alertController.create({
// header: 'Confirm!',
message: "Are you sure you want to exit the app?",
buttons: [
{
text: "Cancel",
role: "cancel",
cssClass: "secondary",
handler: blah => {}
},
{
text: "Close App",
handler: () => {
navigator["app"].exitApp();
}
}
]
});
await alert.present();
}
// function to subscribe to the backbutton event
backButtonEvent(): void {
const event = fromEvent(document, "backbutton");
event.subscribe(async () => {
// When the current route matches a specific page/route in the app where u
// want to exit on back button press.
// else assume the user wants to navigate to a previous page
if(this.router.url === "<example page-url to exit from>") { this.exitAlert()
}
else { this.location.back() }
});
}
Update, for Ionic 5 (Angular)
in your-modal.page.ts
import { ModalController } from '#ionic/angular';
at the top of your modal's .ts file. Then in your constructor you can just denote a -public- relationship to the controller, that way it's accessible by your view.
also in your-modal.page.ts
constructor(
public modalCtrl: ModalController
) {}
Now you can inline the close command:
in your-modal.page.html
<ion-header color="dark">
<ion-toolbar color="dark">
<ion-title>Modal Title</ion-title>
<ion-buttons slot="primary">
<ion-button (click)="modalCtrl.dismiss()">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
Slot "primary" makes the buttons move to the right in iOS.
You can also use the built in function of ionic which is
<ion-back-button>
</ion-back-button>
You can also position the <ion-back-button> to start or end
<ion-buttons slot="start">
<ion-back-button>
</ion-back-button>
</ion-buttons>
for more information about <ion-back-button>
Here's a link

Ionic navbar color dynamically

I get a color string from my server and will change the ion-navbar background navbar dynamically. How can I set the navbar bg color to the color from the variable string?
I know it is possible with fixed defined colors in the variables.scss but I just get the color string e.g. '#00000' from the server so that I cant add it to the variable.scss before.
I tired something like that:
<ion-header>
<team-header [teamColor]="team.teamColor"></team-header>
</ion-header>
-------- header.ts ---------------
#Component({
selector: 'team-header',
templateUrl: 'teamheader.html'
})
export class TeamheaderComponent {
#Input() teamColor;
constructor(public navCtrl: NavController, public viewCtrl: ViewController) {
}
-------- teamheader.html----------- ------------------
<ion-navbar [style.background-color]="teamColor">
...
</ion-navbar>
But it doesnt work.
How can I change the color dynamically?
Maybe it's not the best solution, but it worked for me when I changed the property backgroundcolor of the title class.
home.html
<ion-header>
<ion-navbar>
<ion-title>Home</ion-title>
</ion-navbar>
</ion-header>
home.ts
import { Component, ElementRef } from '#angular/core';
import { NavController, Platform } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController,
public platform: Platform,
public element: ElementRef) {
this.platform.ready().then(() => {
let el = this.element.nativeElement
el = document.getElementsByClassName("title")[0]
el.style.background = 'red';
})
}
}
This seems far from ideal, I would not suggest using server-side code to determine your styling, but in any case, you could try
<ion-header>
<team-header [color]="teamColor"></team-header>
</ion-header>

ionic 2 3, why after scrolling, value of variable doesn't change?

I was wrote this code for showing button after scrolling is more than 500px, but "showButton" didn't get new value.
<ion-content (ionScroll)="onScroll($event)">
<button *ngIf="showButton">Scroll Top</button>
</ion-content>
my.ts file:
showButton= false;
onScroll($event) {
if ($event.scrollTop > 500) {
console.log(this.showButton);
this.showButton= true;
}
}
This console.log shows change of "showButton", but in html it doesn't change.
"showButton" for first time get value "false" but when value change to "true" it can not listen to change, how I can solve this?
From the ionic docs, Scroll Events Scroll events happen outside of
Angular's Zones. This is for performance reasons. So if you're trying
to bind a value to any scroll event, it will need to be wrapped in a
zone.run()
<ion-content (ionScroll)="onScroll($event)">
<button *ngIf="showButton">Scroll Top</button>
</ion-content>
//add import in .ts file
import { Component, NgZone } from '#angular/core';
//in constructor
constructor(
public zone: NgZone,
showButton= false;
onScroll($event) {
this.zone.run(() => {
if ($event.scrollTop > 500) {
console.log(this.showButton);
this.showButton = true;
}
})
}
//Method 1) use boolean variable
<ion-content (ionScroll)="onScroll($event)">
<button *ngIf="showButton">Scroll Top</button>
</ion-content>
showButton:boolean= false;
onScroll($event) {
if ($event.scrollTop > 500) {
console.log(this.showButton);
this.showButton= true;
}
}
OR
//Method 2) use number variable
<ion-content (ionScroll)="onScroll($event)">
<button *ngIf="showButton==1">Scroll Top</button>
</ion-content>
showButton:number= 0;
onScroll($event) {
if ($event.scrollTop > 500) {
console.log(this.showButton);
this.showButton= 1;
}
}