Master/Detail Navigation Within a Tabs Layout in Ionic

Master/Detail Navigation Within a Tabs Layout in Ionic

Follow Josh Morony on

There have been some reasonably big changes to tab-based navigation in Ionic, and one issue that I see pop up quite a lot is creating more complex navigation within individual tabs. Specifically, a lot of people seem to be running into an issue where after navigating to another page inside of a tab, the tab bar will disappear.

In this tutorial, we will be looking at how to create a multi-level master/detail style navigation pattern inside of a tabs layout. This will allow the tab bar to remain in place as pages within a single tab are being navigated, and the current state of a tab will also be remembered when switching back and forth between tabs.

Before We Get Started

Last updated for Ionic 4.0.0

This tutorial assumes you already have a basic level of understanding of Ionic. If you require more introductory level content on Ionic I would recommend checking out my book or the Ionic tutorials on my website.

If you would like to follow along with this tutorial step-by-step, I will be using the tabs starter template that Ionic provides. You can create a new project based on this template by running the following command:

ionic start tabs-sub-navigation tabs --type=angular

1. Create the Detail Pages

First, we need to create some pages. We are going to create ProductList and ViewProduct pages. We will have one of our tabs provide the ability to navigate to a page that would theoretically display a list of products, and then that page will be able to further navigate to a page that displays a specific product.

Run the following commands to create the pages:

ionic g page tab2/ProductList
ionic g page tab2/ViewProduct

It isn’t important that you generate the pages inside of the folder for the tab they will be used in, I think this just helps to keep things organised.

2. Set up the Routes

By default, when you generate a page it will add the routes automatically for you to src/app/app-routing.module.ts. We don’t want that, so make sure to remove those routes.

Make sure that you remove the routes generated for ProductList and ViewProduct from src/app/app-routing.module.ts:

import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', loadChildren: './tabs/tabs.module#TabsPageModule' }
];
@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}

Instead, we are going to add the routes to our tabs routing module.

Modify the routes in src/app/tabs/tabs.router.module.ts to reflect the following:

const routes: Routes = [
  {
    path: 'tabs',
    component: TabsPage,
    children: [
      {
        path: 'tab1',
        children: [
          {
            path: '',
            loadChildren: '../tab1/tab1.module#Tab1PageModule'
          }
        ]
      },
      {
        path: 'tab2',
        children: [
          {
            path: '',
            loadChildren: '../tab2/tab2.module#Tab2PageModule'
          }
        ]
      },
      { path: 'tab2/products', loadChildren: '../tab2/product-list/product-list.module#ProductListPageModule' },
      { path: 'tab2/products/:id', loadChildren: '../tab2/view-product/view-product.module#ViewProductPageModule' },
      {
        path: 'tab3',
        children: [
          {
            path: '',
            loadChildren: '../tab3/tab3.module#Tab3PageModule'
          }
        ]
      },
      {
        path: '',
        redirectTo: '/tabs/tab1',
        pathMatch: 'full'
      }
    ]
  },
  {
    path: '',
    redirectTo: '/tabs/tab1',
    pathMatch: 'full'
  }
];

We have left all of the default tabs we had set up unchanged, but we have added in the two routes for our new pages under the routing information for tab2. Note that these are not being added as children routes of tab2, we are just listing them close to the tab2 routing for organisational purposes. Aside from modifying the loadChildren path to correctly locate the modules for the pages we added, there isn’t anything special about these routes. We have followed a logical URL progression of tab2 -> tab2/products -> tab2/products/:id but that isn’t strictly necessary - you could use whatever you like for the route paths.

3. Implement the Templates

What we have done so far is actually all that is required to set up this style of multi-level tabs navigation. Let’s take a look at implementing the navigation in the templates, though.

Modify src/app/tab2/tab2.page.html to reflect the following:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Tab Two
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>

  <ion-button color="primary" routerLink="/tabs/tab2/products" routerDirection="forward">View Products</ion-button>

</ion-content>

We have just added a simple button that links to the ProductList page that we created. Now let’s take a look at the navigation in that page.

Modify src/app/tabs2/product-list/product-list.page.html to reflect the following:

<ion-header>
  <ion-toolbar>
    <ion-title>ProductList</ion-title>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/tabs/tab2"></ion-back-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content padding>

  <ion-button routerLink='/tabs/tab2/products/2' routerDirection="forward">Product Detail</ion-button>

</ion-content>

Same idea here, except we are linking to a specific product now. We have also added an <ion-back-button> to the header, and it is important that we supply an appropriate defaultHref here incase the user refreshes the application directly to this page (which causes the navigation history to be lost). In that case, the defaultHref will be used when the back button is clicked. If we have a defaultHref of the root of the application, then the application can get in a state where it is stuck. The back button will link back to the default tab page, but the second tab will still be on the ProductList page and you won’t be able to get back to the root tab2 page because the back button will always link back to the tab1 page. Providing an appropriate defaultHref as we have above means we will never get in this situation.

Modify src/app/tabs2/view-product/view-product.page.html to reflect the following:

<ion-header>
  <ion-toolbar>
    <ion-title>ViewProduct</ion-title>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/tabs/tab2/products"></ion-back-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content padding>

</ion-content>

We don’t have anything on this page, but again, it is important to make sure to set up that defaultHref correctly.

Summary

We now have an application where we can navigate within a single tab without breaking the general tabs layout, and each individual tab will also remember its own state/position when navigating between other tabs. The key here is to make sure to define your routes in the routing file for the tabs, not the root routing file for the application.

Check out my latest videos: