February 11, 2019
  • Tutorials
  • Angular
  • Framework

Navigating the Change with Ionic 4 and Angular Router

Simon Grimm

This is a guest post from Simon Grimm, Ionic Developer Expert and educator at the Ionic Academy. Simon also writes about Ionic frequently on his blog Devdactic.

In this tutorial we will look at the new navigation system inside Ionic Framework 4.0 to learn how to use the powerful CLI, to understand how your Ionic 3 logic can be converted to v4, and, finally, the best way to protect pages inside your app if you plan to use an authentication system at any point.

I am Number 4

There is so much to talk about if we want to cover all the Ionic 4 changes, but for today let’s just focus on one of the key aspects of your app: Navigation!

With Ionic 4, your app is using Angular, which already comes with some new additions itself. But now Ionic is also using the standard Angular Router in the background. This means, instead of pushing and popping around inside your app, we have to define paths that are aligned with our pages.

If you are new to this concept, it might look a little scary and you may think, “Why so much code, everything was so easy before…,” but trust me, by the end of this post, you’ll love the new routing and you’ll be ready to migrate your Ionic 3 apps to the new version.

For now, let’s start with a blank Ionic 4 app so we can implement some navigation concepts. Go ahead and run:

npm install -g ionic@latest
ionic start goFourIt blank 

This will create a new project which you can directly run with ionic serve once you are inside that folder. It should bring up a blank app in your browser with just one page.

The Entry Point of Your App

When you inspect the folders of your app, you’ll find one HomePage at the src/app/home path. This is the page you see on screen (compare it’s HTML with what you see if you don’t trust me), but how is it actually loaded?

To understand this we need to open our app/app-routing.module.ts in which we will find:

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

const routes: Routes = [
 { path: '', redirectTo: 'home', pathMatch: 'full' },
 { path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomePageModule) },
];

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

This is the first place for routing information in our app and the place where we can add more information about how our app works. Right now, we have two routes defined inside the array.

The first, is actually a simple redirect that will change the empty path ‘’ to the ‘home’ path, so it’s like going to google.com/ and being redirected to google.com/home.

Inside the definition for the home path we can now spot the loadChildren key in which we supply a path to the module file of our home page. This module file holds some information and imports for the page, but you can think of it as the page that gets displayed.

Ok, cool, we now have a router and are loading a page through a path, so how is this connected with actual HTML or the index page of the app?

If you happen to inspect your index.html the only thing you’ll find is:

<body>
 <app-root></app-root>
</body>

The only thing we display is an app-root, which is still not very clear. This app root is replaced by the first real HTML of our app, which is always inside the app/app.component.html:

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

This is the key to understanding how the routing works: The Angular Router will replace router outlets with the resolved information for a path.

This means inside the body, at the top level, we have this special Ionic router outlet (which is the standard Angular outlet plus some animation extras) wrapped inside a tag for the Ionic app itself. Once we navigate to a certain path, the router will look for a match inside the routes we defined, and display the page inside the right outlet.

We needed this short detour to get a solid understanding about why the things we’ve done work as they do. Now, you are hopefully ready to navigate the change a bit better.

Adding New Pages with the CLI

Because a single page is not yet an app, we need more pages! To do so, you can use the Ionic CLI, which provides a wrapper for the Angular CLI. Right now, we could add 2 additional pages like this:

ionic g page pages/login
ionic g page pages/dashboard
ionic g page pages/details

This command tells the CLI to generate (g) a new page at the path pages/login, pages/dashboard and pages/details. It doesn’t matter that the folder ‘pages’ does not yet exist, the CLI will automatically create them for you.

There’s a whole lot more you can do with the CLI, just take a look at the documentation.

For now, let’s get back to our main goal of implementing navigation inside our app.

After creating pages with the CLI your app-routing.module.ts will automatically be changed, which may or may not help you in some cases. Right now, it also contains routing information for the three new pages we added with the according path of their module.

Changing Your Entry & Navigating (a.k.a Push & Pop)

One thing I often do with my apps is change the initial page to be a different component. To change this, we can simply remove the routing information for the home page, delete its folder, and change the redirect to point to the login page we generated earlier.

Once a user is logged in, the app should then display our dashboard page. The routing for this is fine, so far, and we can leave it like it is.

For the detail page we generated, we do want one addition: URL parameters. Say that you want to pass data from one page to another. To do this, we’d use URL parameters and specify a dynamic slug in the path. For this routing setup, we’ll add :myid.

Your routing should now look like this inside your app-routing.module.ts:

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

const routes: Routes = [
 { path: '', redirectTo: 'login', pathMatch: 'full' },
 { path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule) },
 { path: 'dashboard', loadChildren: () => import('./pages/dashboard/dashboard.module').then(m => m.DashboardPageModule) },
 { path: 'details/:myid', loadChildren: () => import('./pages/details/details.module').then(m => m.DetailsPageModule) }
];

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

With previous Ionic versions you could also supply complete objects with a lot of information to another page, but with the new version this has changed. By using the URL routing, you can (and should) only pass something like an objects ID (to be used in a HTTP request) to the following page.

In order to get the values you need on that page later, simply use a service that holds your information or makes a HTTP request and returns the right info for a given key at any time.

All routing logic is officially in place, so now we only need to add a few buttons to our app that allow us to move around. Let’s start by adding a first button to our pages/login/login.page.html:

<ion-header>
 <ion-toolbar color="primary">
   <ion-title>Login</ion-title>
 </ion-toolbar>
</ion-header>

<ion-content padding>
 <ion-button expand="block" routerLink="/dashboard" routerDirection="root">
   Login
 </ion-button>
</ion-content>

We add a block button which has two important properties:
routerLink: The link/path that should be opened
routerDirection: Determines the animation that takes place when the page changes

After a login, you most certainly want to ditch your initial page and start again with the inside area as a new starting point. In that case, we can use the direction “root,” which looks like replacing the whole view.

If you want to animate forward or backward, you would use forward/back instead. This is what we can add now, because we are already able to move from login to our dashboard. So, let’s add the next two more buttons to the pages/dashboard/dashboard.page.html:

<ion-header>
 <ion-toolbar color="primary">
   <ion-title>Dashboard</ion-title>
 </ion-toolbar>
</ion-header>

<ion-content padding>
 <ion-button expand="block" routerLink="/details/42" routerDirection="forward">
   Details
 </ion-button>

 <ion-button expand="block" routerLink="/" routerDirection="root">
   Logout
 </ion-button>
</ion-content>

This is the same procedure as before—both have the link, but the first button will bring us deeper into our app by going to the details page and using “42” as the ID.

The second button brings us back to the previous login page again by animating a complete exchange of pages.

You can see the difference of the animations below:

Of course, you can also dynamically add the ID for the details page or construct the link like this if you have a variable foo inside your class:

<ion-button expand="block" [routerLink]="['/details/', foo]" routerDirection="forward">

To wrap things up we need to somehow get the value we passed inside our details page, plus your users also need a way to go back from that page to the dashboard.

First things first, getting the value of the path is super easy. We can inject the ActivatedRoute and grab the value inside our pages/details/details.page.ts like this:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
 selector: 'app-details',
 templateUrl: './details.page.html',
 styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {

 myId = null;
  constructor(private activatedRoute: ActivatedRoute) { }

 ngOnInit() {
   this.myId = this.activatedRoute.snapshot.paramMap.get('myid');
 }

}

By doing this, we can get the value, which is part of the paramMapof the current route. Now that we have stored the value, we can also show it inside the current view, plus add a button to the top bar that allows the user to navigate back.

With previous Ionic versions that back button was automatically added. Meaning, the button was there even if we didn’t want it and it was difficult to customize. But with the release of Ionic 4.0, we can control this by adding it ourselves. At the same time, we can also define a defaultHref. This way, if we load our app on that specific page and have no app history, we can navigate back and still have our app function.

The markup for our pages/details/details.page.html looks now like this:

<ion-header>
 <ion-toolbar color="primary">
   <ion-buttons slot="start">
     <ion-back-button defaultHref="/dashboard"></ion-back-button>
   </ion-buttons>
   <ion-title>Details</ion-title>
 </ion-toolbar>
</ion-header>

<ion-content padding>
 My ID is: {{ myId }}
</ion-content>

As you can see, this back-button will now always bring us back to the dashboard, even if we don’t have any history at that point.

By now, the whole navigation setup in our app works pretty flawlessly, but what if we wanted to restrict some routes to only authenticated user? Let’s go ahead and add this.

Protecting Pages with Guards

Many of us often need a way to prevent users from accessing certain pages in our app. This might not be super obvious when only focused on mobile apps, but think about a URL to a website: Your path is exactly this, and some users should simply not be allowed to visit that page if they are not authenticated.

When you deploy your Ionic app as a website, all URLs, right now, could be directly accessed by a user. But here’s an easy way to change it:

We can create something called guard that checks a condition and returns true/false, which allows users to access that page or not. You can generate a guard inside your project with the Ionic CLI:

ionic g guard guards/auth

This generates a new file with the standard guard structure of Angular. Let’s edit guards/auth.guard.ts and change it’s content to:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
 providedIn: 'root'
})
export class AuthGuard implements CanActivate {
 canActivate(
   next: ActivatedRouteSnapshot,
   state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

   let userAuthenticated = false; // Get the current authentication state from a Service!

   if (userAuthenticated) {
     return true;
   } else {
     return false;
   }
 }
}

The guard only has the canActivate() method in which you return a boolean if the page can be accessed. In this code, we simply return false, but a real guard would make an API call or check a token value.

By default this guard is not yet enabled, but now the circle closes as we come back to our initial app routing. So, open the app-routing.module.ts once again and change it to:

import { AuthGuard } from './guards/auth.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
 { path: '', redirectTo: 'login', pathMatch: 'full' },
 { path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule) },
 { path: 'dashboard', loadChildren: () => import('./pages/dashboard/dashboard.module').then(m => m.DashboardPageModule) },
 {
   path: 'details/:myid',
   loadChildren: () => import('./pages/details/details.module').then(m => m.DetailsPageModule),
   canActivate: [AuthGuard]
 }
];

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

You can add an array of these guards to your pages and check for different conditions, but the idea is the same: Can this route be activated by this user?

Because we set it to false without any further checks, right now we could not navigate from the dashboard to the details page.

There’s also more information on guards and resolver functions inside the Ionic Academy, so check it out here!

Where to Go From Here?

We’ve only touched on a few basic elements of the (new) Angular routing concepts that are now applied in Ionic 4, but I hope it was helpful. Now that you’ve walked through this process, this concept should be a lot easier to understand and manage given that your navigation is not scrambled across various pages inside your app.

Also, securing your app or resolving additional data before entering a page becomes a lot easier with the direct paths and the use of child routing groups.

If you’re interested in the concept of routing, or would like to see more explanations around features like the Tabs, Side Menu, or child routes and modals, consider becoming an Ionic Academy member, today!

When you join, you’ll gain access to countless resources that will help you learn everything Ionic, from in-depth training resources to video courses, plus support from an incredibly helpful community.

Until next time, happy coding!


Simon Grimm