How to Navigate in Ionic Modals with ion-nav
This is a guest post from Simon Grimm, Ionic Developer Expert and educator at the Ionic Academy. Simon also recently released the book Practical Ionic, a guide to building real world Ionic applications.
After my session during Ioniconf, there was a question about routing inside Ionic modals that came up during the Q&A. Because the modal is not part of your standard Angular routing, you need a different way to handle navigation inside an overlay that exists outside the rest of your application.
You can achieve the same navigation in all directions (forward, back, root) using the ion-nav component – a highly underrated component that will rescue your day.
The docs already highlight when this component really shines:
“This fits use cases where you could have a modal, which needs its own sub-navigation, without making it tied to the app’s URL.”
Let’s build an app with navigation inside of a modal and make everything work, including the Android back button as well!
Getting Started with our App
As always, let’s start with a blank Ionic app. Go ahead and generate a new module, a component inside that module and finally another page which we will use for our modal logic:
ionic start customNavigation blank --type=angular --capacitor
cd ./customNavigation
ionic g module components/sharedComponents --flat
ionic g component components/modalBase
ionic g page pages/modalContent
You don’t need any external libraries for the functionality we are going to build, everything is included in the Ionic core!
Custom Navigation Component
First of all we focus on creating a base for the modal. The purpose of this base component is to render the ion-nav
component and nothing else. Therefore, start by opening the components/modal-base/modal-base.component.html
and replace everything with:
<ion-nav [root]="rootPage"></ion-nav>
This isn’t a lot, but it is enough to make this component a standalone component for our navigation. Whenever we call this component, we directly want to set the root
of the navigation, and therefore we also declare the variable inside the components/modal-base/modal-base.component.ts
like this:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-modal-base',
templateUrl: './modal-base.component.html',
styleUrls: ['./modal-base.component.scss'],
})
export class ModalBaseComponent implements OnInit {
rootPage: any;
constructor() {}
ngOnInit() {}
}
To use it in the pages of our Ionic application, we now also need to declare and export it in the previously generated components/shared-components.module.ts
:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ModalBaseComponent } from './modal-base/modal-base.component';
import { IonicModule } from '@ionic/angular';
@NgModule({
declarations: [ModalBaseComponent],
imports: [CommonModule, IonicModule],
exports: [ModalBaseComponent],
})
export class SharedComponentsModule {}
With this, we can can now use the component anywhere inside our app when we need a modal with its own navigation stack!
Presenting the Modal
Now we want to actually call the modal, and therefore we can start by importing the just changed module to our home/home.module.ts
:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { HomePage } from './home.page';
import { HomePageRoutingModule } from './home-routing.module';
import { SharedComponentsModule } from '../components/shared-components.module';
import { ModalContentPageModule } from './../pages/modal-content/modal-content.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
HomePageRoutingModule,
SharedComponentsModule,
ModalContentPageModule,
],
declarations: [HomePage],
})
export class HomePageModule {}
We also import the ModalContentPageModule
, because that’s going to be the real content of the modal and we will use the page directly (not just as a string!) when we open the modal. That’s why we need to import the module of the page as well.
Let’s quickly add a button to our view before we start with the real functionality, so open the home/home.page.html
and change it to:
<ion-header>
<ion-toolbar color="primary">
<ion-title>
Ionic Modal Nav
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-button expand="full" (click)="presentModal()">Open Modal</ion-button>
</ion-content>
Now we can present the modal, and we will basically present our ModalBaseComponent
which we define as the component for the modal, but we also pass the rootPage
to the modal (inside the componentProps
), which is now the ModalContentPage
.
That means, the base component will be prevented, but that’s actually only the container for our navigation. At the same time, the content page is used as the root page of that navigation, so that’s going to be the page we see inside our app when the modal opens!
Go ahead now and change the home/home.page.ts
to this:
import { Component } from '@angular/core';
import { ModalController, IonRouterOutlet } from '@ionic/angular';
import { ModalBaseComponent } from '../components/modal-base/modal-base.component';
import { ModalContentPage } from '../pages/modal-content/modal-content.page';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
constructor(
private modalController: ModalController,
private routerOutlet: IonRouterOutlet
) {}
async presentModal() {
const modal = await this.modalController.create({
presentingElement: this.routerOutlet.nativeEl,
component: ModalBaseComponent,
componentProps: {
rootPage: ModalContentPage,
},
});
await modal.present();
}
}
At this point you should be able to open the modal and see the dummy name of the content page. If you get strange errors, restart the serve command. Sometimes it doesn’t immediately pick up the generated files.
Working with the ion-nav Component
Now that we arrived in the modal it’s time for the navigation to new pages. We can perform this both directly from the view, or from code by injecting the ion-nav
.
The ion-nav component comes with a bunch of methods, and we can make use of them to navigate to another page (push), we can go back to our root page from anywhere, or you can use any of the other methods of the component to manipulate the view stack!
We’ll keep it simple for now and only use two of them. Besides that, we will track the level of our page because we are actually reusing the same content page
when we push another page on the stack.
That’s the reason why we define the nextPage
to be our current component – we need a reference to this when we use the ion-nav-link
in the next step.
For now, open the pages/modal-content/modal-content.page.ts
and change it to:
import { Component, OnInit } from '@angular/core';
import { ModalController, IonNav, Platform } from '@ionic/angular';
@Component({
selector: 'app-modal-content',
templateUrl: './modal-content.page.html',
styleUrls: ['./modal-content.page.scss'],
})
export class ModalContentPage implements OnInit {
level = 0;
nextPage = ModalContentPage;
constructor(private modalController: ModalController, private nav: IonNav) {}
ngOnInit() {}
goForward() {
this.nav.push(this.nextPage, { level: this.level + 1 });
}
goRoot() {
this.nav.popToRoot();
}
close() {
this.modalController.dismiss();
}
}
Now we throw the other component that’s used with this custom navigation into the mix, the ion-nav-link component.
As said before, you can do the custom navigation from code (like we did in the snippet before) or directly from the view. And to do so, you just need to define a few things on the component:
component
: The component you want to push or set as rootcomponentProps
: Just like the props when opening a modal – we simply increase the level variable for the next pagerouterDirection
: One of back, forward or root
By specifying and combining these things on the ion-nav-link
, you can basically achieve any kind of navigation change right from here! It’s also a common navigation pattern I share more about inside my book Practical Ionic.
Besides that, we can also still use the standard ion-back-button
in this navigation, but in our example below we will only show it on higher levels and otherwise display the close button for a modal. But that’s just an idea, you could combine this to your likes.
Inside the content of the page we keep the different buttons which either call our navigation from code or use the component with all properties specified to change the routing.
Put the code below into your pages/modal-content/modal-content.page.html
now:
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-button (click)="close()" *ngIf="level == 0">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
<ion-back-button *ngIf="level > 0"></ion-back-button>
</ion-buttons>
<ion-title>My Modal Level: {{ level }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<!-- Not really necessary because of ion-back-button! -->
<ion-nav-link router-direction="back" *ngIf="level > 0">
<ion-button expand="full" color="medium">
<ion-icon name="arrow-back" slot="end"></ion-icon>
ion-nav go back
</ion-button>
</ion-nav-link>
<!-- Open another modal -->
<ion-nav-link
router-direction="forward"
[component]="nextPage"
[componentProps]="{level: level+1}"
>
<ion-button expand="full" color="success">
<ion-icon name="arrow-forward" slot="end"></ion-icon>
Go to level {{ level + 1 }}
</ion-button>
</ion-nav-link>
<!-- Open another modal from code -->
<ion-button expand="full" color="secondary" (click)="goForward()">
<ion-icon name="arrow-forward" slot="end"></ion-icon>
Next Level from Code
</ion-button>
<!-- Go back to the first open modal in the view stack -->
<ion-nav-link
router-direction="root"
[component]="nextPage"
[componentProps]="{level: 0}"
*ngIf="level > 0"
>
<ion-button expand="full" color="tertiary">
<ion-icon name="arrow-back" slot="end"></ion-icon>
Go to root
</ion-button>
</ion-nav-link>
<!-- Go back to first modal from code -->
<ion-button expand="full" color="dark" (click)="goRoot()" *ngIf="level > 0">
<ion-icon name="arrow-back" slot="end"></ion-icon>
Go root from Code
</ion-button>
</ion-content>
Now we got a full navigation concept right inside a modal. We can go forward (basically infinite times), we can pop a page back (in different ways) and we can also navigate back to the root page of the modal.
But I already hear you crying because..
Android Hardware / Software Back Button
Yes, the hardware or software back button on Android won’t really respect our cool navigation concept. What a shame.
But it’s your lucky day, since you have full power over this button and how it behaves inside your Ionic app!
The only thing you need to do is define your own handler for the back button, and perform the correct operation in it. That means, we can check whether we can still go back a page and pop
to the previous page, or otherwise close the modal as we are already on the root page.
Simply change the current constructor of the pages/modal-content/modal-content.page.ts
to this now:
constructor(private modalController: ModalController, private nav: IonNav, private platform: Platform) {
this.platform.backButton.subscribeWithPriority(101, async () => {
let canGoBack = await this.nav.canGoBack();
if (canGoBack) {
this.nav.pop();
} else {
await this.modalController.dismiss();
}
return;
});
}
This overwrites the default behaviour, because the priority of the modal is 100, which means our action is more important!
Capacitor Device Testing
If you finally want to try this on a device, you need to build the app once, add the platforms your want and then run the app:
ionic build
npx cap add ios
npx cap add android
ionic cap run ios --livereload --host=0.0.0.0
While testing I really prefer the livereload of Capacitor, as this will open Android Studio or Xcode, and you only need to deploy to your device once and then enjoy the same livereload that you are already used from your browser!
Conclusion
When you are inside an overlay (modal, popover..) there’s no routing since these elements live outside the pages and the URL routing of the Angular router.
Therefore, the ion-nav and ion-nav-link are a perfect substitution to building your own navigation, like inside a nested menu with multiple levels or other scenarios where you just quickly want to navigate deeper into a page.
If you want to learn more about Ionic with a library of 40+ courses and supportive community, you can also join the Ionic Academy and get access to a ton of learning material.
And don’t forget to subscribe to my YouTube channel for fresh Ionic tutorials coming every week!