Navigating
Learn to navigate from one view to another using the Router-Lite. Also learn about routing context.
This section details the various ways that you can use to navigate from one part of your application to another part. For performing navigation the router-lite offers couple of alternatives, namely the
href
and the load
custom attributes, and the IRouter#load
method.You can use the
href
custom attribute with an a
(anchor) tag, with a string value. When the users click this link, the router-lite performs the navigation. This can be seen in action in the live example below.The example shows that there are two configured routes,
home
and about
, and in markup there are two anchor tags that points to these routes. Clicking those links the users can navigate to the desired components, as it is expected.my-app.html
my-app.ts
<nav>
<a href="home">Home</a>
<a href="about">About</a>
</nav>
import { route } from '@aurelia/router-lite';
import { Home } from './home';
import { About } from './about';
@route({
routes: [
{
path: ['', 'home'],
component: Home,
},
{
path: 'about',
component: About,
},
],
})
export class MyApp {}
You can also use the parameterized routes with the
href
attribute, exactly the same way. To this end, you need to put the parameter value in the path itself and use the parameterized path in the href
attribute. This is shown in the example below.The example shows two configured routes; one with an optional parameter. The markup has three anchor tags as follows:
my-app.html
my-app.ts
<nav>
<a href="home">Home</a>
<a href="about">About</a>
<a href="about/42">About/42</a>
</nav>
import { route } from '@aurelia/router-lite';
import { Home } from './home';
import { About } from './about';
@route({
routes: [
{
path: ['', 'home'],
component: Home,
},
{
path: ['about/:id?'],
component: About,
},
],
})
export class MyApp {}
The last
href
attribute is an example of a parameterized route.While configuring routes, an
id
for the route can be set explicitly. This id
can also be used with the href
attribute. This is shown in the example below.Note that the example set a route id that is different than the defined path. These route-ids are later used in the markup as the values for the
href
attributes.my-app.ts
my-app.html
child1.ts
child2.ts
import { route } from '@aurelia/router-lite';
import { ChildOne } from './child1';
import { ChildTwo } from './child2';
@route({
routes: [
{
id: 'r1',
path: ['', 'c1'],
component: ChildOne,
},
{
id: 'r2',
path: 'c2',
component: ChildTwo,
},
],
})
export class MyApp {}
<nav>
<a href="r1">C1</a>
<a href="r2">C2</a>
</nav>
import { route } from '@aurelia/router-lite';
import { customElement } from '@aurelia/runtime-html';
@customElement({ name: 'gc-11', template: 'gc11' })
class GrandChildOneOne {}
@customElement({ name: 'gc-12', template: 'gc12' })
class GrandChildOneTwo {}
@route({
routes: [
{ id: 'r1', path: ['', 'gc11'], component: GrandChildOneOne },
{ id: 'r2', path: 'gc12', component: GrandChildOneTwo },
],
})
@customElement({
name: 'c-one',
template: `c1 <br>
<nav>
<a href="r1">gc11</a>
<a href="r2">gc12</a>
</nav>
<br>
<au-viewport></au-viewport>`,
})
export class ChildOne {}
import { route } from '@aurelia/router-lite';
import { customElement } from '@aurelia/runtime-html';
@customElement({ name: 'gc-21', template: 'gc21' })
class GrandChildTwoOne {}
@customElement({ name: 'gc-22', template: 'gc22' })
class GrandChildTwoTwo {}
@route({
routes: [
{ id: 'r1', path: ['', 'gc21'], component: GrandChildTwoOne },
{ id: 'r2', path: 'gc22', component: GrandChildTwoTwo },
],
})
@customElement({
name: 'c-two',
template: `c2 <br>
<nav>
<a href="r1">gc21</a>
<a href="r2">gc22</a>
</nav>
<br>
<au-viewport></au-viewport>`,
})
export class ChildTwo {}
Note that using the route-id of a parameterized route with the
href
attribute might be limiting or in some cases non-operational as with href
attribute there is no way to specify the parameters for the route separately. This case is handled by the load
attribute.{path1}[@{viewport-name}][+{path2}[@{sibling-viewport-name}]]
The following live example, demonstrates that.
The example shows the following variations.
<!-- Load the products' list in the first viewport and the details in the second viewport -->
<a href="products+details/${id}">Load products+details/${id}</a>
<!-- Load the details in the first viewport and the products' list in the second viewport -->
<a href="details/${id}+products">Load details/${id}+products</a>
<!-- Specifically target the named viewports -->
<a href="products@list+details/${id}@details">Load products@list+details/${id}@details</a>
<a href="products@details+details/${id}@list">Load products@details+details/${id}@list</a>
<!-- Load only the details in the specific named viewport -->
<a href="details/${id}@details">Load details/${id}@details</a>
Note that using the viewport name in the routing instruction is optional and when omitted, the router uses the first available viewport.
The navigation using
href
attribute always happens in the current routing context; that is, the routing instruction will be successful if and only the route is configured in the current routing parent. This is shown in the example below.my-app.ts
child1.ts
child1.ts
import { route } from '@aurelia/router-lite';
import { ChildOne } from './child1';
import { ChildTwo } from './child2';
import { NotFound } from './not-found';
@route({
routes: [
{
path: ['', 'c1'],
component: ChildOne,
},
{
path: 'c2',
component: ChildTwo,
},
{
path: 'not-found',
component: NotFound,
},
],
fallback: 'not-found',
})
@customElement({ name: 'my-app', template })
export class MyApp {}
import { route } from '@aurelia/router-lite';
import { customElement } from '@aurelia/runtime-html';
@customElement({ name: 'gc-11', template: 'gc11' })
class GrandChildOneOne {}
@customElement({ name: 'gc-12', template: 'gc12' })
class GrandChildOneTwo {}
@route({
routes: [
{ id: 'gc11', path: ['', 'gc11'], component: GrandChildOneOne },
{ id: 'gc12', path: 'gc12', component: GrandChildOneTwo },
],
})
@customElement({
name: 'c-one',
template: `c1 <br>
<nav>
<a href="gc11">gc11</a>
<a href="gc12">gc12</a>
<a href="c2">c2 (doesn't work)</a>
<a href="../c2">../c2 (works)</a>
</nav>
<br>
<au-viewport></au-viewport>`,
})
export class ChildOne {}
import { route } from '@aurelia/router-lite';
import { customElement } from '@aurelia/runtime-html';
@customElement({ name: 'gc-21', template: 'gc21' })
class GrandChildTwoOne {}
@customElement({ name: 'gc-22', template: 'gc22' })
class GrandChildTwoTwo {}
@route({
routes: [
{ id: 'gc21', path: ['', 'gc21'], component: GrandChildTwoOne },
{ id: 'gc22', path: 'gc22', component: GrandChildTwoTwo },
],
})
@customElement({
name: 'c-two',
template: `c2 <br>
<nav>
<a href="gc21">gc21</a>
<a href="gc22">gc22</a>
<a href="c1">c1 (doesn't work)</a>
<a href="../c1">../c1 (works)</a>
</nav>
<br>
<au-viewport></au-viewport>`,
})
export class ChildTwo {}
In the example, the root component has two child-routes (
c1
, c2
) and every child component in turn has 2 child-routes (gc11
, and gc12
and gc21
, and gc22
respectively) of their own. In this case, any href
pointing to any of the immediate child-routes (and thus configured in the current routing parent) works as expected. However, when an href
, like below (refer child1.ts
), is used to navigate from one child component to another child component, it does not work. <a href="c2">c2 (doesn't work)</a>
In such cases, the router-lite offers the following syntax to make such navigation possible.
<a href="../c2">../c2 (works)</a>
That is, you can use
../
prefix to instruct the router to point to the parent routing context. The prefix can also be used multiple times to point to any ancestor routing context. Naturally, this does not go beyond the root routing context.Contextually, note that the example involving route-id also demonstrates the behavior of navigating in the current context. In that example, the root component uses
r1
, and r2
as route identifiers, which are the same identifiers used in the children to identify their respective child-routes. The route-ids are used in the markup with the href
attributes. Despite being the same route-ids, the navigation works because unless specified otherwise, the routing instructions are constructed under the current routing context.By default the router-lite enables usage of the
href
custom attribute, as that ensures that the router-lite handles the routing instructions by default. There might be cases, where you want to avoid that. If you want to globally deactivate the usage of href
, then you can customize the router configuration by setting false
to the useHref
configuration option.To disable/bypass the default handling of router-lite for any particular
href
attribute, you can avail couple of different ways as per your need and convenience.- Using
external
ordata-external
attribute on thea
tag. - Using a non-null value for the
target
, other than the current window name, or_self
.
Other than that, when clicking the link if either of the
alt
, ctrl
, shift
, meta
key is pressed, the router-lite ignores the routing instruction and the default handling of clicking a link takes place.Following example demonstrate these options.
Although the usage of
href
is the most natural choice, it has some limitations. Firstly, it allows navigating in the current routing context. However, a bigger limitation might be that the href
allows usage of only string values. This might be bit sub-optimal when the routes have parameters, as in that case you need to know the order of the parameterized and static segments etc. to correctly compose the string path. In case the order of those segments are changed, it may cause undesired or unexpected results if your application.To support structured way to constructing URL the router-lite offers another alternative namely the
load
attribute. This custom attribute accepts structured routing instructions as well as string-instructions, just like the href
attribute. Before starting the discussion on the features supported exclusively by the load
attribute, let us quickly review the following example of using string-instructions with the load
attribute.The example shows various instances of
load
attribute with various string instructions.<!-- my-app.html -->
<!-- instructions pointing to individual routes -->
<a load="c1">C1</a>
<a load="c2">C2</a>
<!-- instructions involving sibling viewports -->
<a load="c1+c2">C1+C2</a>
<a load="c1@vp2+c2@vp1">C1@vp2+C2@vp1</a>
<!-- child1 -->
<!-- instruction pointing to parent routing context -->
<a load="../c2">../c2</a>
The following sections discuss the various other ways routing instruction can be used with the
load
attribute.Using the bindable
params
property in the load
custom attribute, you can bind the parameters for a parameterized route. The complete URL is then constructed from the given route and the parameters. Following is an example where the route-id is used with bound parameters.The example above configures a route as follows. The route-id is then used in the markup with the bound
params
, as shown in the example below.my-app.ts
my-app.html
import { route } from '@aurelia/router-lite';
import { ChildTwo } from './child2';
@route({
routes: [
{
id: 'r2',
path: ['c2/:p1/foo/:p2?', 'c2/:p1/foo/:p2/bar/:p3'],
component: ChildTwo,
},
],
})
export class MyApp {}
<!-- constructed path: /c2/1/foo/ -->
<a load="route: r2; params.bind: {p1: 1};">C2 {p1: 1}</a>
<!-- constructed path: /c2/2/foo/3 -->
<a load="route: r2; params.bind: {p1: 2, p2: 3};">C2 {p1: 2, p2: 3}</a>
<!-- constructed path: /c2/4/foo/?p3=5 -->
<a load="route: r2; params.bind: {p1: 4, p3: 5};">C2 {p1: 4, p3: 5}</a>
<!-- constructed path: /c2/6/foo/7/bar/8 -->
<a load="route: r2; params.bind: {p1: 6, p2: 7, p3: 8};">C2 {p1: 6, p2: 7, p3: 8}</a>
<!-- constructed path: /c2/9/foo/10/bar/11?p4=awesome&p5=possum -->
<a load="route: r2; params.bind: {p1: 9, p2: 10, p3: 11, p4: 'awesome', p5: 'possum'};">C2 {p1: 9, p2: 10, p3: 11, p4: 'awesome', p5: 'possum'}</a>
An important thing to note here is how the URL paths are constructed for each URL. Based on the given set of parameters, a path is selected from the configured set of paths for the route, that maximizes the number of matched parameters at the same time meeting the parameter constraints.
For example, the third instance (params:
{p1: 4, p3: 5}
) creates the path /c2/4/foo/?p3=5
(instance of 'c2/:p1/foo/:p2?'
path) even though there is a path with :p3
configured. This happens because the bound parameters-object is missing the p2
required parameter in the path pattern 'c2/:p1/foo/:p2/bar/:p3'
. Therefore, it constructs the path using the pattern 'c2/:p1/foo/:p2?'
instead.In other case, the fourth instance provides a value for
p2
as well as a value for p3
that results in the construction of path /c2/6/foo/7/bar/8
(instance of 'c2/:p1/foo/:p2/bar/:p3'
). This case also demonstrates the aspect of "maximization of parameter matching" while path construction.One last point to note here is that when un-configured parameters are included in the
params
object, those are converted into query string.The bindable
route
property in the load
attribute supports binding a class instead of route-id. The following example demonstrates the params
-example using the classes (child1
, child2
) directly, instead of using the route-id.// my-app.ts
import { ChildOne } from './child1';
import { ChildTwo } from './child2';
export class MyApp {
private readonly child1: typeof ChildOne = ChildOne;
private readonly child2: typeof ChildTwo = ChildTwo;
}
<!-- my-app.html -->
<a load="route.bind: child1">C1</a>
<a load="route.bind: child2; params.bind: {p1: 1};">C2 {p1: 1}</a>
<a load="route.bind: child2; params.bind: {p1: 2, p2: 3};">C2 {p1: 2, p2: 3}</a>
<a load="route.bind: child2; params.bind: {p1: 4, p3: 5};">C2 {p1: 4, p3: 5}</a>
<a load="route.bind: child2; params.bind: {p1: 6, p2: 7, p3: 8};">C2 {p1: 6, p2: 7, p3: 8}</a>
<a load="route.bind: child2; params.bind: {p1: 9, p2: 10, p3: 11, p4: 'awesome', p5: 'possum'};">C2 {p1: 9, p2: 10, p3: 11, p4: 'awesome', p5: 'possum'}</a>
You can see this in action below.
Just like the
href
attribute, the load
attribute also supports navigating in the current routing context by default. The following example shows this where the root component has two child-routes with r1
and r2
route-ids and the child-components in turn defines their own child-routes using the same route-ids. The load
attributes also use the route-ids as routing instruction. The routing works in this case, because the routes are searched in the same routing context.However, this default behavior can be changed by binding the
context
property of the load
custom attribute explicitly. To this end, you need to bind the instance of IRouteContext
in which you want to perform the navigation. The most straightforward way to select a parent routing context is to use the parent
property of the IRouteContext
. The current IRouteContext
can be injected using the @IRouteContext
in the class constructor. Then one can use context.parent
, context.parent?.parent
etc. to select an ancestor context.export class ChildOne {
private readonly parentCtx: IRouteContext;
public constructor(@IRouteContext ctx: IRouteContext) {
this.parentCtx = ctx.parent;
}
}
Such ancestor context can then be used to bind the
context
property of the load
attribute as follows.<a load="route: r2; context.bind: parentCtx">c2</a>
The following live example demonstrate this behavior.
Note that even though the
ChildOne
defines a route with r2
route-id, specifying the context
explicitly, instructs the router-lite to look for a route with r2
route-id in the parent routing context.Using the
IRouteContext#parent
path to select the root routing context is somewhat cumbersome when you intend to target the root routing context. For convenience, the router-lite supports binding null
to the context
property which instructs the router to perform the navigation in the root routing context.<a load="route: r2; context.bind: null">Go to root c2</a>
This is shown in the following example.
When the route context selection involves only ancestor context, then the
../
prefix can be used when using string instruction. This also works when using the route-id. The following code snippets shows, how the previous example can be written using the ../
prefix.<a load="route: ../r2">c2</a>
When using the
load
attribute, you can also leverage the bindable active
property which is true
whenever the associated route, bound to the href
is active. In the following example, when a link in clicked and thereby the route is activated, the active*
properties are bound from the views to true
and thereby applying the .active
-CSS-class on the a
tags.<style>
a.active {
font-weight: bolder;
}
</style>
<nav>
<a
load="route:foo; params.bind:{id: 1}; active.bind:active1"
active.class="active1"
>foo/1</a
>
<a load="route:foo/2; active.bind:active2" active.class="active2">foo/2</a>
</nav>
<au-viewport></au-viewport>
This can also be seen in the live example below.
The
active
bindable can be used for other purposes, other than adding CSS classes to the element. However, if that's what you need mostly the active
property for, you may choose to configure the activeClass
property in the router configuration. When configured, the load
custom attribute will add that configured class to the element when the associated routing instruction is active.Along with the custom attributes on the markup-side, the router-lite also offers the
IRouter#load
method that can be used to perform navigation, with the complete capabilities of the JavaScript at your disposal. To this end, you have to first inject the router into your component. This can be done by using the IRouter
decorator on your component constructor method as shown in the example below.import { IRouter, IRouteableComponent } from '@aurelia/router-lite';
export class MyComponent {
public constructor(@IRouter private readonly router: IRouter) { }
}
Now you are ready to use the
load
method, with many supported overloads at your disposal. These are outlined below.The easiest way to use the
load
method is to use the paths directly.router.load('c1')
router.load('c2')
router.load('c2/42')
router.load('c1+c2')
router.load('c1@vp2+c2@vp1')
With respect to that, this method supports the string instructions supported by the
href
and the load
attribute. This is also shown in the example below.There is a major important difference regarding the context selection in the
IRouter#load
method and the href
and load
custom attributes. By default, the custom attributes performs the navigation in the current routing context (refer the href
and load
attribute documentation). However, the load
method always use the root routing context to perform the navigation. This can be observed in the ChildOne
and ChildTwo
components where the load
method is used the following way to navigate from ChildOne
to ChildTwo
and vice versa. As the load
API uses the the root routing context by default, such routing instructions works. In comparison, note that with href
we needed to use the ..
prefix or with load
method we needed to set the context to null.
// in ChildOne
router.load('c2');
// in ChildTwo
router.load('c1');
However, on the other hand, you need to specify the routing context, when you want to navigate inside the current routing context. The most obvious use case is when you issue routing instruction for the child-routes inside a parent component. This can also be observed in
ChildOne
and ChildTwo
components where a specific context is used as part of the navigation options to navigate to the child routes.// in ChildOne
router.load('gc11', { context: this });
// in ChildTwo
router.load('gc21', { context: this });
An array of paths (string) can be used to load components into sibling viewports. The paths can be parameterized or not non-parameterized.
router.load(['c1', 'c2']);
router.load(['c1', 'c2/21']);
This is shown in the example below.
The
load
method also support non-string routing instruction.Using custom elements
You can use the custom element classes directly for which the routes have been configured. Multiple custom element classes can be used in an array to target sibling viewports.
router.load(ChildOne);
router.load([ChildOne, ChildTwo]);
router.load(GrandChildOneOne, { context: this });
This can be seen in action in the live example below.
Using custom element definitions
You can use the custom element definitions for which routes have been configured. Multiple definitions can be used in an array to target sibling viewports.
import { CustomElement } from '@aurelia/runtime-html';
router.load(CustomElement.getDefinition(ChildOne));
router.load([
CustomElement.getDefinition(ChildOne),
CustomElement.getDefinition(ChildTwo)
]);
router.load(
CustomElement.getDefinition(GrandChildOneOne),
{ context: this }
);
This can be seen in action in the live example below.
Using a function to return the view-model class
Similar to route configuration, for
load
you can use a function that returns a class as routing instruction. This looks like as follows.router.load(() => ChildOne);
router.load([() => ChildOne, () => ChildTwo]);
router.load(() => GrandChildOneOne, { context: this });
This can be seen in action in the live example below.
Using
import()
Similar to route configuration, for
load
you can use an import()
statement to import a module. This looks like as follows.router.load(import('./child1')); // uses the default or first non-default import
router.load([
import('./child1'),
import('./child2').then(m => m.Child2) // selective import
]);
This can be seen in action in the live example below.
Note that because invoking the
import()
function returns a promise, you can also use a promise directly with the load
function.router.load(Promise.resolve({ ChildOne }));
Using a viewport instruction
Any kind of routing instruction used for the
load
method is converted to a viewport instruction tree. Therefore, you can also use a (partial) viewport instruction directly with the load
method. This offers maximum flexibility in terms of configuration, such as routing parameters, viewports, children etc. Following are few examples, how the viewport instruction API can be used.// using a route-id
router.load({ component: 'c1' });
// using a class
router.load({ component: ChildTwo });
// load sibling routes
router.load([
// use custom element definition
{ component: CustomElement.getDefinition(ChildOne) },
// use a function returning class
{ component: () => ChildTwo, params: { id: 42 } },
]);
// load sibling routes with nested children and parameters etc.
router.load([
// using path
{
component: 'c1',
children: [{ component: GrandChildOneTwo }],
viewport: 'vp2',
},
// using import
{
component: import('./child2'),
params: { id: 21 },
children: [{ component: GrandChildTwoTwo }],
viewport: 'vp1',
},
]);
This can be seen in the example below.
Along with using the routing instructions, the
load
method allows you to specify different navigation options on a per-use basis. One of those, the context
, you have already seen in the examples in the previous sections. This section describes other available options.title
The
title
property allows you to modify the title as you navigate to your route. This looks like as follows.router.load(Home, { title: 'Some title' });
Note that defining the
title
like this, overrides the title defined via the route configuration. This can also be seen in the action below where a random title is generated every time.titleSeparator
As the name suggests, this provides a configuration option to customize the separator for the title parts. By default router-lite uses
|
(pipe) as separator. For example if the root component defines a title 'Aurelia'
and has a route /home
with title Home
, then the resulting title would be Home | Aurelia
when navigating to the route /home
. Using this option, you can customize the separator.router.load(Home, { titleSeparator: '-' });
This can also be seen in the action below where a random title separator is selected every time.
queryParams
This option lets you specify an object to be serialized to a query string. This can be used as follows.
// the generated URL: /home?foo=bar&fizz=buzz
router.load(
'home',
{
queryParams: {
foo: 'bar',
fizz: 'buzz',
}
}
);
This can be seen in the live example below.
fragment
Like the
queryParams
, using the fragment
option, you can specify the hash fragment for the new URL. This can be used as follows.// the generated URL: /home#foobar
router.load(
'home',
{
fragment: 'foobar'
}
);
This can be seen in the live example below.
context
As by default, the
load
method performs the navigation relative to root context, when navigating to child routes, the context needs to be specified. This navigation option has also already been used in various examples previously. Various types of values can be used for the context
.The easiest is to use the custom element view model instance. If you are reading this documentation sequentially, then you already noticed this. An example looks like as follows.
router.load('child-route', { context: this });
Here is one of the previous example. Take a look at the
child1.ts
or child2.ts
that demonstrates this.You can also use an instance of
IRouteContext
directly. One way to grab the instance of IRouteContext
is to get it inject via constructor
using the @IRouteContext
decorator. An example looks like as follows.import { IRouteContext, IRouter, Params, route } from '@aurelia/router-lite';
import { customElement } from '@aurelia/runtime-html';
@customElement({ name: 'gc-21', template: 'gc21' })
class GrandChildTwoOne {}
@customElement({ name: 'gc-22', template: 'gc22' })
class GrandChildTwoTwo {}
@route({
routes: [
{ id: 'gc21', path: ['', 'gc21'], component: GrandChildTwoOne },
{ id: 'gc22', path: 'gc22', component: GrandChildTwoTwo },
],
})
@customElement({
name: 'c-two',
template: `c2 <br>
id: \${id}
<nav>
<button click.trigger="load('gc21', true)">Go to gc21</button>
<button click.trigger="load('gc22', true)">Go to gc22</button>
<button click.trigger="load('c1')" >Go to c1 </button>
</nav>
<br>
<au-viewport></au-viewport>`,
})
export class ChildTwo {
private id: string;
public constructor(
@IRouter private readonly router: IRouter,
// injected instance of IRouteContext
@IRouteContext private readonly context: IRouteContext
) {}
private load(route: string, useCurrentContext: boolean = false) {
void this.router.load(
route,
useCurrentContext
? {
// use the injected IRouteContext as navigation context.
context: this.context
}
: undefined
);