Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/api/route-redirect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ The route redirect has two configurable properties:
- `from`
- `to`

It redirects "from" a URL "to" another URL. When the defined `ion-route-redirect` rule matches, the router will redirect from the path specified in the `from` property to the path in the `to` property. In order for a redirect to occur the `from` path needs to be an exact match to the navigated URL.
It redirects "from" a URL "to" another URL. When the defined `ion-route-redirect` rule matches, the router will redirect from the path specified in the `from` property to the path in the `to` property. In order for a redirect to occur the `from` path needs to be an exact match to the navigated URL, unless `from` contains a `*`. A `*` matches the rest of the path from that segment on, as long as the path has at least as many segments as `from`, so `/admin/*` matches `/admin/posts` but not `/admin`.

To render a "not found" page for unknown paths instead of redirecting, use a [catch-all route](route.mdx#catch-all-routes).

## Multiple Route Redirects {/* #multiple-route-redirects */}

Expand All @@ -58,7 +60,7 @@ If the user navigates to `/admin` the router will redirect to `/login` and stop
will NOT redirect if the user navigates to `/admin/posts` -->
<ion-route-redirect from="/admin" to="/login"></ion-route-redirect>

<!-- By adding the wilcard character (*), the redirect will match
<!-- By adding the wildcard character (*), the redirect will match
any subpath of admin -->
<ion-route-redirect from="/admin/*" to="/login"></ion-route-redirect>
```
Expand Down
115 changes: 115 additions & 0 deletions docs/api/route.mdx

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't need to be done now but we should consider adding a Navigation/Routing guide under JavaScript like the other frameworks have:

I think that would be a better place for this kind of information.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,121 @@ Note: this component should only be used with vanilla and Stencil JavaScript pro

:::

## Catch-all Routes {/* #catch-all-routes */}

:::note

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 `/`.

```html
<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:

<Tabs groupId="framework" defaultValue="javascript" values={[{ value: 'javascript', label: 'Javascript' }, { value: 'stencil', label: 'Stencil' }]}>

<TabItem value="javascript">

```javascript
customElements.define(
'page-not-found',
class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-content>
<p>No page found at /${this.pathMatch}</p>
</ion-content>
`;
}
}
);
```

</TabItem>

<TabItem value="stencil">

```typescript
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>
);
}
}
```

</TabItem>

</Tabs>

### Matching Priority {/* #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 {/* #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`](route-redirect.mdx), 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`:

```html
<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 */}

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.
Expand Down
5 changes: 5 additions & 0 deletions docs/api/router.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ interface RouterCustomEvent extends CustomEvent {

## Usage {/* #usage */}

The last route is a [catch-all route](route.mdx#catch-all-routes) that renders `page-not-found` for any URL that no other route matches.

```html
<ion-router>
<ion-route-redirect from="/" to="/schedule"></ion-route-redirect>

<ion-route component="page-tabs">
<ion-route url="/schedule" component="tab-schedule">
<ion-route component="page-schedule"></ion-route>
Expand All @@ -100,6 +104,7 @@ interface RouterCustomEvent extends CustomEvent {
<ion-route url="/account" component="page-account"></ion-route>
<ion-route url="/signup" component="page-signup"></ion-route>
<ion-route url="/support" component="page-support"></ion-route>
<ion-route url="*" component="page-not-found"></ion-route>
</ion-router>
```

Expand Down
Loading