Aurelia Router Demo
Note that a more recent post revisits this demo in CodeSandbox. In terms of the code nothing has changed and the description below remains valid.
A demonstration of basic routing with Aurelia. Select the Profile menu item on the navigation bar in the demo to see three levels of navigation that we will be creating with the aurelia-router.
Route Configuration
By convention, Aurelia looks for a method called configureRouter
on a view-model. This method gets passed two arguments…
For example, the view-model for app.js
, the top level navigation for the application, is a follows…
// app.js
export class App {
configureRouter(config, router) {
config.title = 'Aurelia Router Demo';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/home', nav: true, title: 'Home' },
{
route: 'profile',
name: 'profile',
moduleId: 'profile/profile',
nav: true,
title: 'Profile'
},
{
route: 'settings',
name: 'settings',
moduleId: 'settings/settings',
nav: true,
title: 'Settings'
}
]);
this.router = router;
}
}
Title
First an optional title
property is set on the config
object, this value is applied to the <title>
element in the <head>
of the html document. Note that the framework automatically handles this for you and appends that title to the title of the current route, in this case the default route home
.
<title>Home | Aurelia Router Demo</title>
Routes
Next we create routes using the config.map
method which expects an array of objects, each object defines a route. Route properties are described below as per the documentation. Note that there are additional properties but only the ones used here are described.
route
: The route pattern to match against incoming URL fragments, or an array of patternsname
: A unique name for the route that may be used to identify the route when generating URL fragmentsmoduleId
: The moduleId of the view model that should be activated for this routenav
: When specified, this route will be included in the [[Router.navigation]] nav model. Useful for dynamically generating menus or other navigation elementstitle
: The document title to set when this route is active
Setting the Default Route
[”, ‘home’]: indicates this is the default route, home is an alias
The default route is defined by the empty string ''
, this is the route that will be loaded on application startup.
{route: '', name: 'home', moduleId: 'home/home', nav: true, title: 'Home'}
This means when we navigate to the following address in the browser, the home
route will be loaded as there is an empty string to the right of the hash tag.
http://localhost:9000/#
You can provide an alias for the default route if desired.
{route: ['', 'home'], name: 'home', moduleId: 'home/home', nav: true, title: 'Home'}
This will allow you to navigate to the default route with either of the following url’s…
http://localhost:9000/#
http://localhost:9000/#/home
If you require more than one alias you can do that too.
{route: ['', 'home', 'welcome'], name: 'home', moduleId: 'home/home', nav: true, title: 'Home'}
http://localhost:9000/#
http://localhost:9000/#/home
http://localhost:9000/#/welcome
Reference to the Router
Finally, notice that we create a reference to the router so we can access it in the corresponding view, app.html
, allowing us to build navigation menus dynamically.
this.router = router;
Creating the View
Routes are defined in the view-model app.js
. In the corresponding view, app.html
, we require a navigation
element and render it in the view, binding the router
so it is available to it.
<!-- app.html -->
<template>
<require from="components/navigation.html"></require>
<h1>Aurelia Router Demo</h1>
<navigation router.bind="router" class="primary-navigation"></navigation>
<div class="page-host">
<router-view>
<!-- Top level views are rendered here -->
</router-view>
</div>
</template>
Dynamic Navigation Menu
In the navigation
template, note that a bindable
attribute is specified on the <template>
element which provides a reference to the router
. A repeater is used to iterate the navigation
array on the router
object. Each item in the navigation
array is a NavModel
which corresponds to each of the routes configured in app.js
. This provides access to properties such as title
which is used for each menu item and href
which is the url fragment i.e. the route that we want to navigate to e.g. home
, profile
and settings
.
The isActive
property is used to set a class name on the item that corresponds to the currently active route.
<!-- components/navigation.html -->
<template bindable="router">
<nav>
<ul>
<li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
<a href.bind="row.href">${row.title}</a>
</li>
</ul>
</nav>
</template>
This generates the following markup…
<ul>
<li class="au-target active" au-target-id="1">
<a href.bind="row.href" class="au-target" au-target-id="2" href="#/">Home</a>
</li>
<li class="au-target" au-target-id="1">
<a href.bind="row.href" class="au-target" au-target-id="2" href="#/profile">Profile</a>
</li>
<li class="au-target" au-target-id="1">
<a href.bind="row.href" class="au-target" au-target-id="2" href="#/settings">Settings</a>
</li>
</ul>
The text content of each <a>
element is the value of the title
property defined in the route configuration and the href
attribute is populated with the url fragment as defined by the route
property. Note that the first <li>
element has a class of active
, this denotes the active route allowing it to be styled accordingly.
Child Routers
Armed with the knowledge of how to create the top level routes for an application there is not a great deal more to learn in order to create child routes. In fact the only difference in this example is how the default route is specified. First lets have a quick recap of the three levels of navigation in the example.
- Home / Profile / Settings
- Account / Emails / Notifications
- Username / Password
When Profile is selected in the menu, we route to 'profile/profile'
as defined by the moduleId
for the profile
route. If we take a look at profile.js
we see that it is a view-model that implements the configureRouter
method and defines 3 routes, this is the second level of navigation.
Note that the only difference from the top level route definition is how the default route is defined. We still use the empty string to denote the default route but a second property, redirect
, determines which route to use.
// profile/profile.js
export class Profile {
configureRouter(config, router) {
config.map([
{ route: '', redirect: 'account' },
{
route: 'account',
name: 'account',
moduleId: 'profile/account/account',
nav: true,
title: 'Account'
},
{
route: 'emails',
name: 'emails',
moduleId: 'profile/emails/emails',
nav: true,
title: 'Emails'
},
{
route: 'notifications',
name: 'notifications',
moduleId: 'profile/notifications/notifications',
nav: true,
title: 'Notifications'
}
]);
this.router = router;
}
}
Similarly, when Account is selected from the second level navigation we route to 'profile/account/account'
. The view-model for account.js
also defines child routes creating a third level of navigation.
// profile/account/account.js
export class Account {
configureRouter(config, router) {
config.map([
{ route: '', redirect: 'username' },
{
route: 'username',
name: 'username',
moduleId: 'profile/account/username/username',
nav: true,
title: 'Username'
},
{
route: 'password',
name: 'password',
moduleId: 'profile/account/password/password',
nav: true,
title: 'Password'
}
]);
this.router = router;
}
}
Both profile.js
and account.js
have their corresponding views which dynamically create the navigation menu.
Thoughts
Routing is essential in any single-page application and is a hard problem to solve well. Aurelia appears to have solved it very well. Has it ever been easier to configure routes than this?
It would be straightforward to quickly flesh out the routes for a large application, something that has been painful with other approaches in my experience where a huge amount of boilerplate is required to hook everything together.
Of course there is a lot more to routing than is covered here. For example, hooking into application lifecycle methods where you can access route params or dynamically route to another view depending on a given condition, amongst other features. More stuff to do.