Ionic 4 — Hiding & Showing Tabs on certain pages

Jordan Benge
Dec 23, 2018 · 8 min read

Ionic 4 has come with a number of changes. The biggest change, in my opinion, is how we utilize Tabs and Navigation. In ionic 3 we could add an attribute to certain tabs called tabsHideOnSubPages which allowed us to do exactly that — hide the tab bar on subpages of that tab. Unfortunately, that has not been implemented for Ionic 4 as of yet, though it has been opened as a feature request.


So what should we do for Ionic 4?

Let's get started

We’ll create the service provider by running the command ionic g service core/tabs. After the command finishes, you’ll notice a new folder in your src/app/ folder, with our new service provider, called tabs.service.ts.

Next, we’ll need to import the service into our app.modules.ts provider array, as well as importing it into our app.component.ts file, in our constructor.

app.module.ts

@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
...
],
providers: [
TabsService
...
],
bootstrap: [AppComponent],
})
export class AppModule {
}

app.component.ts

@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
})
export class AppComponent {
constructor(public tabs: TabsService) {
...
}
...
}

Configure the TabsService provider

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

@Injectable({
providedIn: 'root'
})
export class TabsService {

constructor() { }
}

Next, we’ll create a new private variable called hideTabBarPages. This is a string array, that we’ll use to tell Ionic what pages to hide our tab bar on. As you can see, I want to hide the tab bar on a page called new-group. But you can change this to include whatever you want.

private hideTabBarPages: string[] = [
'new-group',
];

Next, in your constructor, you should import the Router and the Platform providers like so:

constructor(private router: Router, private platform: Platform) {
this.platform.ready().then(() => {
this.navEvents();
});
}

All this does is wait for the platform to be ready before we do anything, as we can’t hide tabs that aren’t created yet, now can we?

Now we’ll go ahead and hook up the above function called NavEvents , which will handle our navigation events for us. It’s a pretty simple function, and really only a container for the router.events subscription.

this.router.events
.pipe(filter((e) => e instanceof NavigationEnd))
.subscribe((e: any) => {
this.showHideTabs(e);
});

All we’re doing is subscribing to the router.events Observable, filtering out the events so that we only get the NavigationEnd events, and then run another function with the event data. The reason we’re filtering out certain events is that Angular triggers a number of lifecycle events, whenever we navigate to a page. If we didn’t do this, the inner function this.showHideTabs(e) would be called numerous times with every page change, which isn’t ideal.

Now we should create our function showHideTabs(). This is what will handle whether the tabs should be shown, or hidden depending on the page we navigated to.

private showHidetabs(e: NavigationEnd) {
...
}

The e: NavigationEnd event will have the following information in it. This will be different depending on the page that you’re navigating to.

{
id: 13
url: "/tabs/groups/new-group?type=msg"
urlAfterRedirects: "/tabs/groups/new-group?type=msg"
}

First, we need to get the event URL in a format that we can use.

const urlArray = e.url.split('/');
// Result: urlArray: ["", "tabs", "groups", "new-group?type=group"]

Next, we’ll grab the last page URL in the path.

// Grab the last page url.
const pageUrl = urlArray[urlArray.length - 1];
// Result: new-group?type=group

Since we’re left with new-group?type=group, we need to remove the parameters, since we only want the page name. The parameters that I’m passing are shown after the ? character, as ?type=group. You should include this regardless, as it future proof’s your code.

// Remove the parameters from the URL.
const page = pageUrl.split('?')[0];
// Result: new-group

One of the last things we need to do in this function is check if we should hide this particular page or not.

// Check if we should hide or show tabs.
const shouldHide = this.hideTabBarPages.indexOf(page) > -1;
// Result: true

Lastly, we’ll need to actually hide the tabs if, or show them if they’ve been previously hidden.

shouldHide ? this.hideTabs() : this.showTabs()

The logic is mostly explained in the comments, but if you’re confused about the line shouldHide ? this.hideTabs() : this.showTabs() , this is called a ternary operator, which is a fancy way of writing an if / else statement.

Basically, it says that if ShouldHide is true, then run this.hideTabs() if shouldHide is false run this.showTabs() .


Smoother Transitions (optional, recommended):

// Not ideal to set the timeout, but I haven't figured out a better method to wait until the page is in transition...
try {
setTimeout(() => shouldHide ? this.hideTabs() : this.showTabs(), 300);
} catch (err) {}

The try / catch statement is just for any weird errors that pop up. Sometimes the tabs aren’t initialized yet, even after we check if the platform is ready in the constructor. So this will prevent our application from throwing out an error, unnecessarily.


Handling Pages with Route Parameters (Optional, recommended):

product-details/5 is a good example of what we’re looking for.

Create a new public variable calledrouteParamPages, it’s a string array, same as our previous one hideTabBarPages. This will house the pages that have route-parameters, such as 'product-details/:id' where product-details is the parent page and :id is the route parameter that you are passing to it. It’ll end up looking something like this:

routeParamPages: string[] = [
'product-details',
];

Now in our ShowHideTabs function, we need to add a few extra things. The first is a new variable called pageUrlParent.

This is similar to the pageUrl variable, but instead of grabbing the page we’re navigating to from the urlArray, we’re going to grab the parent of that page (one level up). So in my example above, product-details is the parent, and :id is the child. It’s the same page technically, but naming conventions are hard sometimes, and I wanted it to be somewhat clear what we were doing.

Add pageUrlParent somewhere above shouldHideTabs.

const pageUrlParent = urlArray[urlArray.length - 2];

Next, we’ll add in a check to see if the parentPage is in our routeParamPages array. This will tell us if the page in question is a routeParamPage, and if it needs to be hidden. Add this in right above shouldHideTabs

// handle param pages
const hideParamPage = this.routeParamPages.indexOf(pageUrlParent) > -1 && !isNaN(Number(page));

If you want to know what’s happening here, we’re first checking if he pageUrlParent is in our routeParamPages array. This would be product-details in my example and would evaluate as being true.

And then we are converting the page variable to a Number. You could use parseInt, but it has a lot of odd edge-cases that I didn’t like, so I decided to use Number(page) instead. If the page we are navigating to has any non-numeric characters, this will evaluate to Nan which is Not A Number. Lastly, we are checking that the converted Number(page) is not Nan through !isNan(Number(page)). Then we just have to tack on an or statement to our shouldHide variable and we’re back in business!

const shouldHideTabs = this.hideTabBarPages.indexOf(page) > -1 || hideParamPage;

Create the hideTabs() & showTabs() functions.

public hideTabs() {
const tabBar = document.getElementById('myTabBar');
if (tabBar !== null && tabBar.style.display !== 'none') tabBar.style.display = 'none';
}

public showTabs() {
const tabBar = document.getElementById('myTabBar');
if (tabBar !== null && tabBar.style.display !== 'flex') tabBar.style.display = 'flex';
}

The functions are almost identical, and could be shortened into one function if you wanted, but I kept them separate for the articles sake.

You may notice that we’re checking to make sure that the tabBar is not null. This is to ensure that our service doesn’t throw an error when navigating on a page that doesn’t have a tabBar such as app-login, walkthrough, etc... this was suggested by Bryan Martinez in the comment section. Since we use the&& operator, when the first statement evaluates to false, it will stop evaluating the if statement, through the use of short circuiting.

I am also checking if the tab bar has a certain display value before changing it. flex for if we’re going to show the tabs and none if we’re going to hide them. This just prevents us from doing an unnecessary operation and possibly causing the tabs to flicker.


Lastly

<ion-tab-bar #myTabBar id="myTabBar" slot="bottom">

That’s it!

Here is the full service, if you just want to copy-paste it, we’ve all done it before ;-)

import { Injectable } from '@angular/core';
import { filter } from 'rxjs/operators';
import { NavigationEnd, Router } from '@angular/router';
import { Platform } from '@ionic/angular';

@Injectable({
providedIn: 'root',
})
export class TabsService {

hideTabBarPages = [
'new-group',
];
routeParamPages: string[] = [
'product-details',
];
constructor(private router: Router, private platform: Platform) {
this.platform.ready().then(() => {
console.log('Core service init');
this.navEvents();
});
}

public hideTabs() {
const tabBar = document.getElementById('myTabBar');
if (tabBar.style.display !== 'none') tabBar.style.display = 'none';
}

public showTabs() {
const tabBar = document.getElementById('myTabBar');
if (tabBar.style.display !== 'flex') tabBar.style.display = 'flex';
}

// A simple subscription that tells us what page we're currently navigating to.
private navEvents() {
this.router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe((e: any) => {
console.log(e);
this.showHideTabs(e);
});
}

private showHideTabs(e: any) {
// Result: e.url: "/tabs/groups/new-group?type=group"

// Split the URL up into an array.
const urlArray = e.url.split('/');
// Result: urlArray: ["", "tabs", "groups", "new-group?type=group"]
// Grab the parentUrl
const pageUrlParent = urlArray[urlArray.length - 2];
// Grab the last page url.
const pageUrl = urlArray[urlArray.length - 1];
// Result: new-group?type=group

const page = pageUrl.split('?')[0];
// Result: new-group
// Check if it's a routeParamPage that we need to hide on
const hideParamPage = this.routeParamPages.indexOf(pageUrlParent) > -1 && !isNaN(Number(page));
// Check if we should hide or show tabs.
const shouldHide = this.hideTabBarPages.indexOf(page) > -1 || hideParamPage;
// Result: true

// Not ideal to set the timeout, but I haven't figured out a better method to wait until the page is in transition...
try {
setTimeout(() => shouldHide ? this.hideTabs() : this.showTabs(), 300);
} catch (err) {
}
}
}

Questions?

Who am I? My name is Jordan Benge, I am a Software Developer who loves helping others and contributing to Open-Source. I’ve been working in the Ionic Framework since Ionic 1, and have tried to keep up to date on the latest and greatest when it comes to Hybrid Mobile App Development.

If you enjoyed this story, please click the 👏 button and share to help others find it! Feel free to leave a comment below if you need any help.

Jordan Benge

Written by

My name is Jordan Benge, I’m a freelance developer, and sometimes like to write helpful articles on Medium for the dazed and confused developers — like myself.

Jordan Benge

Written by

My name is Jordan Benge, I’m a freelance developer, and sometimes like to write helpful articles on Medium for the dazed and confused developers — like myself.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store