Nested Routes

Creating a Tabs Layout with Angular Routing and Ionic 4



·

Recently, I published a tutorial that talked through how to use Angular routing in an Ionic/Angular application when Ionic 4.x is released. Although using Angular style routing is not required if you want to upgrade to Ionic 4, there are a lot of benefits to doing so (and some features that were available with standard Ionic navigation in Ionic 3.x will only be available through Angular routing with Ionic 4.x).

We discussed the basics of routing, and I also published another tutorial that describes implementing a typical master detail pattern using Angular routing in Ionic. If you have not already read these posts, I would recommend reading them first if you do not already understand how Angular routing works.

The general concept of Angular routing is reasonably straight-forward: you specify a particular route that the user would navigate to using a URL, and you associate a component with that route. Whilst routing can be simple, it is also a very powerful system and can get quite complex. One particular scenario that gets a little more complex is using nested routes to create a tabs layout.

In this tutorial, we are going to discuss how to create a layout that uses the Ionic tabs component in an Ionic/Angular application with Angular routing. I am going to get into the theory side of things a little here, but some of this stuff you don’t strictly need to know. If you are just interested in getting tabs working, you might want to skip to the second section. The set up for tabs using Angular routing, especially when also using lazy loading, does look quite complicated. However, keep in mind that you won’t really have to set this up yourself as it will just be available in one of the official starter templates.

Nested Routes

In Ionic 3.x, we would typically set up tabs like this:

<ion-tabs>
  <ion-tab [root]="tab1Root" tabTitle="One" tabIcon="navigate"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="Two" tabIcon="person"></ion-tab>
  <ion-tab [root]="tab3Root" tabTitle="Three" tabIcon="bookmarks"></ion-tab>
</ion-tabs>

All we need to do is supply the root property of an <ion-tab> with a reference to the page component that we want. However, with Angular routing in Ionic 4, we are relying on the URL for navigation and so this format doesn’t suit.

This is where we get into the concept of nested routes. In a basic Ionic 4 application, we would just have a single <ion-router-outlet> in the template for the root component:

<ion-app>
  <ion-router-outlet></ion-router-outlet>
</ion-app>

We have our routes defined with their associated components, and whenever a particular route is activated that component will be displayed wherever the <ion-router-outlet> is. However, we can also have multiple outlets in our application, and we can nest those outlets inside of each other.

In the case of a tabs layout, we would use nested outlets. We would have our main <ion-router-outlet> that would still sit in the root components template, but then we would have additional outlets for displaying the various tabs inside of our main outlet.

If we were to consider a simplified structure of our tabs layout, where we have a page called HomePage acting as our tabs page which contains three tabs, it might look like this:

<ion-app>
    <ion-router-outlet>
        <app-page-home>
            <ion-tabs>
                <ion-tab>
                    <ion-router-outlet name="one">
                        <app-page-one>
                            <etc></etc>
                            ...
                            <etc></etc>
                        </app-page-one>
                    </ion-router-outlet>
                </ion-tab>
                <ion-tab>
                    <ion-router-outlet name="two">

                    </ion-router-outlet>      
                </ion-tab>
                <ion-tab>
                    <ion-router-outlet name="three">

                    </ion-router-outlet>      
                </ion-tab>
            </ion-tabs>
        </app-page-home>
    </ion-router-outlet>
</ion-app>

This is what the DOM might look like once the routing system has injected the appropriate components into the outlets. The <app-page-home> component gets injected into the main <ion-router-outlet>, and that home component defines three more outlets inside of an <ion-tabs> component. Notice that only one of the nested outlets has a component injected into it. Only the tab that is activated will have its component injected into the <ion-router-outlet>.

Creating Nested Routes for Tabs in Ionic

Now that we understand the basic idea and structure, let’s talk through how to set this up in an application. Let’s assume that we have a component called HomePage that we are using to hold our tabs, and we will define three tabs for that page.

Our main app-routing.module.ts file would look something like this:

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

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

}

This is just a standard routing set up with lazy loading. We want our HomePage to be loaded by default when the application is opened. The template for home.page.html might look something like this:

<ion-tabs>
  <ion-tab label="One" icon="navigate" href="/tabs/(one:one)">
    <ion-router-outlet name="one"></ion-router-outlet>
  </ion-tab>
  <ion-tab label="Two" icon="person" href="/tabs/(two:two)">
    <ion-router-outlet name="two"></ion-router-outlet>
  </ion-tab>
  <ion-tab label="Three" icon="bookmarks" href="/tabs/(three:three)">
    <ion-router-outlet name="three"></ion-router-outlet>
  </ion-tab>
</ion-tabs>

We have our three nested outlets added here, but unlike our main outlet, we have given each one a name. This will allow us to specify which outlet we want to use when defining our routes. You may also notice the weird syntax we are using to link to the routes: /tabs/(one:one). This is just a way to specify which outlet to use, and follows the format of (outlet:path). If we specify (one:one) that means that we want to use the outlet with a name of one and we also want the route path to be one. You could define multiple paths for a single outlet, in that case, you might have a link like: /tabs/(one:detail).

Now let’s take a look at the routes we might define in our home-routing.module.ts file:

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

import { HomePage } from './home.page';

const routes: Routes = [
    { 
        path: 'tabs', 
        component: HomePage,
        children: [
            {
                path: 'one',
                outlet: 'one',
                loadChildren: '../one/one.module#OneModule'
            },
            {
                path: 'two',
                outlet: 'two',
                loadChildren: '../two/two.module#TwoModule'
            },
            {
                path: three',
                outlet: three',
                loadChildren: '../three/three.module#ThreeModule'
            }
        ]
    },
    {
        path: '',
        redirectTo: '/tabs/(one:one)'        
    }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class HomePageRoutingModule { }

This doesn’t look too dissimilar to standard route setups, except that we are listing our tab routes as children of the route that loads the HomePage component. Each of these child routes also specifies which outlet should be used to display the component. You should specify the name of the <ion-router-outlet> that you want the component to display in. If you wanted to display multiple components inside of a single tab you could also add those additional routes here (i.e. if you wanted to navigate to additional pages within a tab).

An important difference here is that rather than just using a blank path for our HomePage component, we are using a path of tabs and then we redirect the default path to that using:

path: '',
redirectTo: '/tabs/(one:one)'    

The reason for this is that if we were to attempt to just use the default path and load up the component, the component wouldn’t know what tab to display as we haven’t specified a particular outlet/path. With the redirect, we are able to supply the outlet and path using the (outlet:path) syntax, and you can use this to specify the tab that should be loaded by default.

There is one last piece to the puzzle, and that is the routing file for the tab itself. If we were to look at one-routing.module.ts it might look like this:

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

import { OnePage } from './one.page';

const routes: Routes = [
  { path: '', component: OnePage, outlet: 'one' }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class OnePageRoutingModule { }

This just looks the same as any other lazily loaded component, again with the one key difference that the outlet needs to be specified to determine which <ion-router-outlet> the tab is displayed in.

Summary

The biggest changes in migrating to Ionic 4 are mostly to do with routing, and as I mentioned, this particular example does look quite intimidating compared to the old approach. If everything I have discussed here isn’t making sense to you, don’t worry, for most people just using the default tabs implementation in the starter template is going to work fine without you having to modify it very much. The benefit of this added complexity is that for the people who do want to create more complicated navigation structures, they have a lot more flexibility now.

What to watch next...