Angular Navigation
This guide covers how routing works in an app built with Ionic and Angular.
The Angular Router is one of the most important libraries in an Angular application. Without it, apps would be single view/single context apps or would not be able to maintain their navigation state on browser reloads. With Angular Router, we can create rich apps that are linkable and have rich animations (when paired with Ionic of course). Let's look at the basics of the Angular Router and how we can configure it for Ionic apps.
A simple Route
For most apps, having some sort of route is often required. The most basic configuration looks a bit like this:
import { RouterModule } from '@angular/router';
@NgModule({
imports: [
...
RouterModule.forRoot([
{ path: '', component: LoginComponent },
{ path: 'detail', component: DetailComponent },
])
],
})
The simplest breakdown for what we have here is a path/component lookup. When our app loads, the router kicks things off by reading the URL the user is trying to load. In our sample, our route looks for ''
, which is essentially our index route. So for this, we load the LoginComponent
. Fairly straight forward. This pattern of matching paths with a component continues for every entry we have in the router config. But what if we wanted to load a different path on our initial load?
Handling Redirects
For this we can use router redirects. Redirects work the same way that a typical route object does, but just includes a few different keys.
[
{ path: '', redirectTo: 'login', pathMatch: 'full' },
{ path: 'login', component: LoginComponent },
{ path: 'detail', component: DetailComponent },
];
In our redirect, we look for the index path of our app. Then if we load that, we redirect to the login
route. The last key of pathMatch
is required to tell the router how it should look up the path.
Since we use full
, we're telling the router that we should compare the full path, even if ends up being something like /route1/route2/route3
. Meaning that if we have:
{ path: '/route1/route2/route3', redirectTo: 'login', pathMatch: 'full' },
{ path: 'login', component: LoginComponent },
And load /route1/route2/route3
we'll redirect. But if we loaded /route1/route2/route4
, we won't redirect, as the paths don't match fully.
Alternatively, if we used:
{ path: '/route1/route2', redirectTo: 'login', pathMatch: 'prefix' },
{ path: 'login', component: LoginComponent },
Then load both /route1/route2/route3
and /route1/route2/route4
, we'll be redirected for both routes. This is because pathMatch: 'prefix'
will match only part of the path.
Navigating to different routes
Talking about routes is good and all, but how does one actually navigate to said routes? For this, we can use the routerLink
directive. Let's go back and take our simple router setup from earlier:
RouterModule.forRoot([
{ path: '', component: LoginComponent },
{ path: 'detail', component: DetailComponent },
]);
Now from the LoginComponent
, we can use the following HTML to navigate to the detail route.
<ion-header>
<ion-toolbar>
<ion-title>Login</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button [routerLink]="['/detail']">Go to detail</ion-button>
</ion-content>
The important part here is the ion-button
and routerLink
directive. RouterLink works on a similar idea as typical href
s, but instead of building out the URL as a string, it can be built as an array, which can provide more complicated paths.
We also can programmatically navigate in our app by using the router API.
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
...
})
export class LoginComponent {
constructor(private router: Router){}
navigate(){
this.router.navigate(['/detail'])
}
}
Both options provide the same navigation mechanism, just fitting different use cases.
Navigating using LocationStrategy.historyGo
Angular Router has a LocationStrategy.historyGo method that allows developers to move forward or backward through the application history. Let's take a look at an example.
Say you have the following application history:
/pageA
--> /pageB
--> /pageC
If you were to call LocationStrategy.historyGo(-2)
on /pageC
, you would be brought back to /pageA
. If you then called LocationStrategy.historyGo(2)
, you would be brought to /pageC
.
An key characteristic of LocationStrategy.historyGo()
is that it expects your application history to be linear. This means that LocationStrategy.historyGo()
should not be used in applications that make use of non-linear routing. See Linear Routing versus Non-Linear Routing for more information.
Lazy loading routes
Now the current way our routes are setup makes it so they are included in the same chunk as the root app.module, which is not ideal. Instead, the router has a setup that allows the components to be isolated to their own chunks.
import { RouterModule } from '@angular/router';
@NgModule({
imports: [
...
RouterModule.forRoot([
{ path: '', redirectTo: 'login', pathMatch: 'full' },
{ path: 'login', loadChildren: () => import('./login/login.module').then(m => m.LoginModule) },
{ path: 'detail', loadChildren: () => import('./detail/detail.module').then(m => m.DetailModule) }
])
],
})
While similar, the loadChildren
property is a way to reference a module by using native import instead of a component directly. In order to do this though, we need to create a module for each of the components.
...
import { RouterModule } from '@angular/router';
import { LoginComponent } from './login.component';
@NgModule({
imports: [
...
RouterModule.forChild([
{ path: '', component: LoginComponent },
])
],
})