ion-route
The route component takes a component and renders it when the Browser URL matches the url property.
Note: this component should only be used with vanilla and Stencil JavaScript projects. For Angular projects, use ion-router-outlet and the Angular router.
Catch-all Routes
Catch-all routes are supported starting in Ionic v9.1. In earlier versions, url="/:any" can be used to catch paths whose first segment does not match another route. Unknown paths below a route that does match, such as /admin/settings, still render that route.
A url that ends in * is a catch-all route. It matches any remaining path, which is useful for showing a "not found" page. The part of the path matched by * is passed to the component as the pathMatch property, without a leading /.
<ion-router>
<ion-route url="/" component="page-home"></ion-route>
<ion-route url="/admin" component="page-admin"></ion-route>
<ion-route url="/admin/users/:id" component="page-admin-user"></ion-route>
<ion-route url="/admin/*" component="page-admin-not-found"></ion-route>
<ion-route url="*" component="page-not-found"></ion-route>
</ion-router>
With these routes:
| Path | Renders | pathMatch |
|---|---|---|
/admin | page-admin | |
/admin/users/1 | page-admin-user | |
/admin/settings | page-admin-not-found | settings |
/admin/users/1/edit | page-admin-not-found | users/1/edit |
/settings | page-not-found | settings |
The component reads pathMatch like any other property:
- Javascript
- Stencil
customElements.define(
'page-not-found',
class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-content>
<p>No page found at /${this.pathMatch}</p>
</ion-content>
`;
}
}
);
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'page-not-found',
})
export class PageNotFound {
@Prop() pathMatch?: string;
render() {
return (
<ion-content>
<p>No page found at /{this.pathMatch}</p>
</ion-content>
);
}
}
Matching Priority
Without a catch-all, a route that has a url and no child routes also matches paths that have extra segments after its url. Routes with an empty url, such as / or a default child route, only match exactly. When a catch-all route matches the path, routes that only match by ignoring extra segments are no longer considered, even if another route wins.
When a catch-all route and a route without a catch-all both match the whole path, the route with more fixed segments wins. A fixed segment is one that is not a parameter, such as admin. If they have the same number, the route without the catch-all wins.
Using the routes from the example above:
| Path | Without catch-alls | With catch-alls | Why |
|---|---|---|---|
/admin | page-admin | page-admin | /admin and /admin/* both have one fixed segment, so the route without the catch-all wins. |
/admin/users/1 | page-admin-user | page-admin-user | /admin/users/:id has more fixed segments than /admin/*. |
/admin/settings | page-admin | page-admin-not-found | page-admin only matched by ignoring settings, so the catch-all wins. |
/admin/users/1/edit | page-admin-user | page-admin-not-found | page-admin-user only matched by ignoring edit, so the catch-all wins. |
/settings | No match | page-not-found | No other route matches. |
When more than one catch-all route matches, the one with the most fixed segments wins. Segments from parent routes count toward this total, so a catch-all route nested inside another route can handle unknown paths below it.
Redirects run before routes are matched. A redirect from * applies to every path, so <ion-route-redirect from="*"> takes precedence over <ion-route url="*">.
Caveats
A catch-all route also matches its own prefix, so /admin/* matches /admin with pathMatch set to an empty string. This differs from ion-route-redirect, where from="/admin/*" does not match /admin.
A * is only treated as a wildcard when it is the last segment of the url. On a route with child routes, the * is ignored and the route matches as if its url did not end in *.
The router sets pathMatch on catch-all routes and overwrites any value passed in componentProps, so do not pass it there.
If the component of a catch-all route is a tab in ion-tabs, the tab does not receive pathMatch. Instead, nest the catch-all route as a child of the tab's route so it renders in that tab's ion-router-outlet:
<ion-route url="/docs" component="tab-docs">
<ion-route component="page-docs"></ion-route>
<ion-route url="*" component="page-docs-not-found"></ion-route>
</ion-route>
Navigation Hooks
Navigation hooks can be used to perform tasks or act as navigation guards. Hooks are used by providing functions to the beforeEnter and beforeLeave properties on each ion-route. Returning true allows navigation to proceed, while returning false causes it to be cancelled. Returning an object of type NavigationHookOptions allows you to redirect navigation to another page.
Interfaces
interface NavigationHookOptions {
/**
* A valid path to redirect navigation to.
*/
redirect: string;
}
Usage
- Javascript
- Stencil
- Vue
<ion-router>
<ion-route url="/home" component="page-home"></ion-route>
<ion-route url="/dashboard" component="page-dashboard"></ion-route>
<ion-route url="/new-message" component="page-new-message"></ion-route>
<ion-route url="/login" component="page-login"></ion-route>
</ion-router>
const dashboardPage = document.querySelector('ion-route[url="/dashboard"]');
dashboardPage.beforeEnter = isLoggedInGuard;
const newMessagePage = document.querySelector('ion-route[url="/dashboard"]');
newMessagePage.beforeLeave = hasUnsavedDataGuard;
const isLoggedInGuard = async () => {
const isLoggedIn = await UserData.isLoggedIn(); // Replace this with actual login validation
if (isLoggedIn) {
return true;
} else {
return { redirect: '/login' }; // If a user is not logged in, they will be redirected to the /login page
}
};
const hasUnsavedDataGuard = async () => {
const hasUnsavedData = await checkData(); // Replace this with actual validation
if (hasUnsavedData) {
return await confirmDiscardChanges();
} else {
return true;
}
};
const confirmDiscardChanges = async () => {
const alert = document.createElement('ion-alert');
alert.header = 'Discard Unsaved Changes?';
alert.message = 'Are you sure you want to leave? Any unsaved changed will be lost.';
alert.buttons = [
{
text: 'Cancel',
role: 'Cancel',
},
{
text: 'Discard',
role: 'destructive',
},
];
document.body.appendChild(alert);
await alert.present();
const { role } = await alert.onDidDismiss();
return role === 'Cancel' ? false : true;
};
import { Component, h } from '@stencil/core';
import { alertController } from '@ionic/core';
@Component({
tag: 'router-example',
styleUrl: 'router-example.css',
})
export class RouterExample {
render() {
return (
<ion-router>
<ion-route url="/home" component="page-home"></ion-route>
<ion-route url="/dashboard" component="page-dashboard" beforeEnter={isLoggedInGuard}></ion-route>
<ion-route url="/new-message" component="page-new-message" beforeLeave={hasUnsavedDataGuard}></ion-route>
<ion-route url="/login" component="page-login"></ion-route>
</ion-router>
);
}
}
const isLoggedInGuard = async () => {
const isLoggedIn = await UserData.isLoggedIn(); // Replace this with actual login validation
if (isLoggedIn) {
return true;
} else {
return { redirect: '/login' }; // If a user is not logged in, they will be redirected to the /login page
}
};
const hasUnsavedDataGuard = async () => {
const hasUnsavedData = await checkData(); // Replace this with actual validation
if (hasUnsavedData) {
return await confirmDiscardChanges();
} else {
return true;
}
};
const confirmDiscardChanges = async () => {
const alert = await alertController.create({
header: 'Discard Unsaved Changes?',
message: 'Are you sure you want to leave? Any unsaved changed will be lost.',
buttons: [
{
text: 'Cancel',
role: 'Cancel',
},
{
text: 'Discard',
role: 'destructive',
},
],
});
await alert.present();
const { role } = await alert.onDidDismiss();
return role === 'Cancel' ? false : true;
};
<template>
<ion-router>
<ion-route url="/home" component="page-home"></ion-route>
<ion-route url="/dashboard" component="page-dashboard" :beforeEnter="isLoggedInGuard"></ion-route>
<ion-route url="/new-message" component="page-new-message" :beforeLeave="hasUnsavedDataGuard"></ion-route>
<ion-route url="/login" component="page-login"></ion-route>
</ion-router>
</template>
<script>
import { alertController } from '@ionic/vue';
const isLoggedInGuard = async () => {
const isLoggedIn = await UserData.isLoggedIn(); // Replace this with actual login validation
if (isLoggedIn) {
return true;
} else {
return { redirect: '/login' }; // If a user is not logged in, they will be redirected to the /login page
}
};
const hasUnsavedDataGuard = async () => {
const hasUnsavedData = await checkData(); // Replace this with actual validation
if (hasUnsavedData) {
return await confirmDiscardChanges();
} else {
return true;
}
};
const confirmDiscardChanges = async () => {
const alert = await alertController.create({
header: 'Discard Unsaved Changes?',
message: 'Are you sure you want to leave? Any unsaved changed will be lost.',
buttons: [
{
text: 'Cancel',
role: 'Cancel',
},
{
text: 'Discard',
role: 'destructive',
},
],
});
await alert.present();
const { role } = await alert.onDidDismiss();
return role === 'Cancel' ? false : true;
};
</script>
Properties
beforeEnter
| Description | A navigation hook that is fired when the route tries to enter. Returning true allows the navigation to proceed, while returning false causes it to be cancelled. Returning a NavigationHookOptions object causes the router to redirect to the path specified. |
| Attribute | undefined |
| Type | (() => NavigationHookResult | Promise<NavigationHookResult>) | undefined |
| Default | undefined |
beforeLeave
| Description | A navigation hook that is fired when the route tries to leave. Returning true allows the navigation to proceed, while returning false causes it to be cancelled. Returning a NavigationHookOptions object causes the router to redirect to the path specified. |
| Attribute | undefined |
| Type | (() => NavigationHookResult | Promise<NavigationHookResult>) | undefined |
| Default | undefined |
component
| Description | Name of the component to load/select in the navigation outlet (ion-tabs, ion-router-outlet) when the route matches.The value of this property is not always the tagname of the component to load, in ion-tabs it actually refers to the name of the ion-tab to select. |
| Attribute | component |
| Type | string |
| Default | undefined |
componentProps
| Description | A key value { 'red': true, 'blue': 'white'} containing props that should be passed to the defined component when rendered. |
| Attribute | undefined |
| Type | undefined | { [key: string]: any; } |
| Default | undefined |
url
| Description | Relative path that needs to match in order for this route to apply. Accepts paths similar to expressjs so that you can define parameters in the url /foo/:bar where bar would be available in incoming props. |
| Attribute | url |
| Type | string |
| Default | '' |
Events
| Name | Description | Bubbles |
|---|---|---|
ionRouteDataChanged | Used internally by ion-router to know when this route did change. | true |
Methods
No public methods available for this component.
CSS Shadow Parts
No CSS shadow parts available for this component.
CSS Custom Properties
No CSS custom properties available for this component.
Slots
No slots available for this component.