Earlier this week, I published an article that discussed how and why you should use Angular routing with Ionic 4.x. To summarise, the main points from that article were that:
- Angular routing primarily relies on defining a bunch of routes that link to specific components/pages, and then using the current URL to determine which route should be activated
- Push/pop navigation will still be available through Ionic’s components, but it is not recommended for Angular
- There are big benefits to switching to using Angular routing with Ionic/Angular
If you haven’t already read the previous article I would recommend that you do if you do not already understand how to use Angular routing and lazy loading. This article will be easier to understand with that background knowledge.
Once you have routing set up in your application, there isn’t really much to worry about. You just set up the routes, then link between them as you please. However, using Angular routing does change the ways in which you can pass data between pages. Implementing a Master/Detail pattern (i.e. where you display a list of items on one page and the details for that item on another page) is extremely common in Ionic applications, and I think this is a concept a lot of developers will quickly butt heads with after switching to Angular routing.
I will be covering how to deal with this scenario in Ionic applications that use Angular routing.
Passing Data to Another Page with Push/Pop
In Ionic 3.x, it is common to use the NavController
and NavParams
to pass data around in an Ionic application. Typically, if you wanted to implement a master/detail pattern you might have a list set up like this:
<ion-item (click)="viewDetail(todo)" *ngFor="let todo of todos">
{{ todo.title }}
</ion-item>
Each todo item has an event binding that will pass the specific todo item onto a function, which might look like this:
viewDetail(todo){
this.navCtrl.push('DetailPage', {todo: todo});
}
This pushes the new page on to the navigation stack and sends the todo along with it. That object can then be retrieved using NavParams
on the DetailPage
.
Passing Data to Another Page with Angular Routing
There are a few methods for navigating between routes and passing data with Angular routing. Primarily, we would often just be using a routerLink
:
<ion-button [routerLink]="'/detail/' + todo.id"></ion-button>
If we want to supply data to the page we are linking to, we can just add it to the URL (assuming that a route is set up to accept that data). You can also navigate to another page programmatically using the navigateBack
, navigateForward
, or navigateRoot
methods of the new NavController
that Ionic provides.
this.navCtrl.navigateForward(`/detail/${todo.id}`);
We are using string substitution here to add the data to the URL (since it is neat), but you could form the URL string any way you want. You could also use the navigate
method that the Angular router provides if you like, which will allow you to supply additional parameters. This allows you to create more complex routes, like this example from the Angular documentation:
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], {
relativeTo: this.route,
});
In most cases, you would likely just be using routerLink
and where it is necessary to get functions involved you would mostly only need to use navigateBack
, navigateForward
, or navigateRoot
(and it is important to use these methods through the NavController
as they will apply the screen transition animations correctly in Ionic). In all of these cases, we are relying on sending the data through the URL. So, if we want to pass some object from one page to another, this isn’t a suitable method to do that. You could send all of the data for your object through the URL by turning your object into a JSON string, but it’s not an entirely practical solution in a lot of cases.
Implementing Master/Detail with Angular Routing
Usually, the best way to tackle this situation is to simply pass an id
through the URL, and then use that id
to grab the rest of the data through a provider/service. We would have our routes set up like this:
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', loadChildren: './pages/home/home.module#HomeModule' },
{
path: 'detail/:id',
loadChildren: './pages/detail/detail.module#DetailModule',
},
];
This will allow our Detail
page to accept an id
parameter. All we need to do is supply that id
in the URL:
http://localhost:8100/detail/12
If all we are doing is passing an id
, that means we need to be able to grab the entire object from somewhere using that id
. To do that, we would create a service to hold/retrieve that data:
todo.service.ts
import { Injectable } from '@angular/core';
interface Todo {
id: string,
title: string,
description: "string"
}
@Injectable({
providedIn: 'root'
})
export class TodoService {
public todos: Todo[] = [];
constructor() {
// Set some test todos
this.todos = [
{ id: 'todo-one', title: 'Todo One', description: "'Do Stuff' },"
{ id: 'todo-two', title: 'Todo Two', description: "'Do Stuff' },"
{ id: 'todo-three', title: 'Todo Three', description: "'Do Stuff' },"
{ id: 'todo-four', title: 'Todo Four', description: "'Do Stuff' },"
{ id: 'todo-five', title: 'Todo Five', description: "'Do Stuff' },"
{ id: 'todo-six', title: 'Todo Six', description: "'Do Stuff' },"
{ id: 'todo-seven', title: 'Todo Seven', description: "'Do Stuff' }"
];
}
getTodo(id): Todo {
return this.todos.find(todo => todo.id === id);
}
}
This is just a very simple service with some dummy data, but it will allow us to access the data, and retrieve a specific “todo” by using the getTodo
function. We would inject that service into our master page:
home.page.ts:
import { Component } from '@angular/core';
import { TodoService } from '../../services/todo.service';
@Component({
selector: 'app-page-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
constructor(private todoService: TodoService){
}
}
and then we would then display this data in our master template like this:
home.page.html
<ion-list lines="none">
<ion-item
[routerLink]="'/detail/' + todo.id"
detail="true"
*ngFor="let todo of todoService.todos"
>
<ion-label>{{ todo.title }}</ion-label>
</ion-item>
</ion-list>
We are looping over the data int the TodoService
, and for each todo we set up an routerLink
that passes just the id
of the todo onto the route. This allows each item to be clicked to activate the detail
route with the appropriate id
.
Then on our detail page, we need to grab that id
and then use it to retrieve the appropriate todo:
detail.page.ts
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { TodoService } from '../../services/todo.service';
@Component({
selector: 'app-page-detail',
templateUrl: 'detail.page.html',
styleUrls: ['detail.page.scss'],
})
export class DetailPage {
private todo;
constructor(private route: ActivatedRoute, private todoService: TodoService){
}
ionViewWillEnter(){
let todoId = this.route.snapshot.paramMap.get('id');
this.todo = this.todoService.getTodo(todoId);
}
}
We can inject the ActivatedRoute
to get a snapshot of the id
value that is provided as a URL parameter. We then use that id
to grab the specific todo we are interested in from the TodoService
, and then we can display it in our detail template:
detail.page.html
<ion-header>
<ion-toolbar>
<ion-title> {{ todo?.title }} </ion-title>
<ion-buttons slot="start">
<ion-back-button defaultHref="/"></ion-back-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content padding>
<p>{{ todo?.description }}</p>
</ion-content>
Summary
The method for creating a master/detail pattern in Ionic 4 with Angular routing is noticeably more difficult because we are now required to have this intermediary service. However, I don’t think this should be viewed as a negative, it is a good thing in the way that it:
- Encourages you to design your application in a more modular way – if you have some entity in your application like a “Todo” it should have a service to handle operations associated with it anyway
- It allows for easier navigation by URL. If you link directly to a detail page everything will just work as expected, since all the information required is there. In applications where you pass the object through
NavParams
, if you link directly to a detail page it will be missing required information (and so you need to handle that case).