Basic Configuration
To use Aurelia's router, your component view must have a <router-view></router-view>
element. In order to configure the router, the component's view-model requires a configureRouter()
function.
<template>
<ul repeat.for="nav of router.navigation">
<li class="${nav.isActive ? 'active' : ''}"><a href.bind="nav.href">${nav.title}</a></li>
</ul>
<router-view></router-view>
</template>
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' },
{ route: 'users/:id/detail', name: 'userDetail', moduleId: 'users/detail' },
{ route: 'files/*path', name: 'files', moduleId: 'files/index', nav: 0, title: 'Files', href:'#files' }
]);
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' },
{ route: 'users/:id/detail', name: 'userDetail', moduleId: 'users/detail' },
{ route: 'files/*path', name: 'files', moduleId: 'files/index', nav: 0, title: 'Files', href:'#files' }
]);
}
}
config.map()
adds route(s) to the router. Although onlyroute
,name
,moduleId
,href
andnav
are shown above there are other properties that can be included in a route. The interface name for a route isRouteConfig
. You can also useconfig.mapRoute()
to add a single route.route
- is the pattern to match against incoming URL fragments. It can be a string or array of strings. The route can contain parameterized routes or wildcards as well.- Parameterized routes match against a string with a
:token
parameter (ie: 'users/:id/detail'). An object with the token parameter's name is set as property and passed as a parameter to the route view-model'sactivate()
function. - A parameter can be made optional by appending a question mark
:token?
(ie:users/:id?/detail
would match bothusers/3/detail
andusers/detail
). When an optional parameter is missing from the url, the property passed toactivate()
isundefined
. - Wildcard routes are used to match the "rest" of a path (ie: files/*path matches files/new/doc or files/temp). An object with the rest of the URL after the segment is set as the
path
property and passed as a parameter toactivate()
as well.
- Parameterized routes match against a string with a
name
- is a friendly name that you can use to reference the route with, particularly when using route generation.moduleId
- is the id (usually a relative path) of the module that exports the component that should be rendered when the route is matched.href
- is a conditionally optional property. If it is not defined then route is used. If route has segments then href is required as in the case of files because the router does not know how to fill out the parameterized portions of the pattern.nav
- is a boolean or number property. When set to true the route will be included in the router's navigation model. When specified as number, the value will be used in sorting the routes. This makes it easier to create a dynamic menu or similar elements. The navigation model will be available as array ofNavModel
inrouter.navigation
. These are the available properties inNavModel
:isActive
flag which will be true when the associated route is active.title
which will be prepended in html title when the associated route is active.href
can be used ona
tag.config
is the object defined inconfig.map
.settings
is equal to the propertysettings
ofconfig
object.router
is a reference for AppRouter.- Other properties includes
relativeHref
andorder
.
title
- is the text to be displayed as the document's title (appears in your browser's title bar or tab). It is combined with therouter.title
and the title from any child routes.titleSeparator
- is the text that will be used to join thetitle
and any activeroute.title
s. The default value is' | '
.
Navigation States
The router contains a number of additional properties that indicate the current status of router navigation. These properties are only set on the base router, i.e. not in child routers. Additionally, these properties are all with respect to browser history which extends past the lifecycle of the router itself.
router.isNavigating
:true
if the router is currently processing a navigation.router.isNavigatingFirst
:true
if the router is navigating into the app for the first time in the browser session.router.isNavigatingNew
:true
if the router is navigating to a page instance not in the browser session history. This is triggered when the user clicks a link or the navigate function is called explicitly.router.isNavigatingForward
:true
if the router is navigating forward in the browser session history. This is triggered when the user clicks the forward button in their browser.router.isNavigatingBack
:true
if the router is navigating back in the browser session history. This is triggered when the user clicks the back button in their browser or when thenavigateBack()
function is called.router.isNavigatingRefresh
:true
if the router is navigating due to a browser refresh.router.isExplicitNavigation
:true
if the router is navigating due to explicit call to navigate function(s).router.isExplicitNavigationBack
:true
if the router is navigating due to explicit call to navigateBack function.
Webpack Configuration
When using Webpack it is necessary to help Aurelia create a path that is resolvable by the loader. This is done by wrapping the moduleId
string with PLATFORM.moduleName()
. You can import PLATFORM
into your project from either aurelia-framework
or aurelia-pal
.
import { PLATFORM } from "aurelia-framework";
export class App {
configureRouter(config, router) {
config.map([
{ route: ['', 'home'], name: 'home', moduleId: PLATFORM.moduleName('home') }
]);
this.router = router;
}
}
import { PLATFORM } from "aurelia-framework";
export class App {
configureRouter(config: RouterConfiguration, router: Router): void {
config.map([
{ route: ['', 'home'], name: 'home', moduleId: PLATFORM.moduleName('home') }
]);
}
}
See
Managing dependencies
for more information on PLATFORM.moduleName()
.
Options
Push State
Set config.options.pushState
to true
to activate push state and add
a base tag
to the head of your html document. If you're using JSPM, RequireJS or a similar module loader, you will also need to configure it with a base url, corresponding to your base tag's href
. Finally, be sure to set the config.options.root
to match your base tag's setting.
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
config.options.pushState = true;
config.options.root = '/';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.options.pushState = true;
config.options.root = '/';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
PushState requires server-side support. This configuration is different depending on your server setup. For example, if you are using Webpack DevServer, you'll want to set the devServer
historyApiFallback
option to true
. If you are using ASP.NET Core, you'll want to call routes.MapSpaFallbackRoute
in your startup code. See you preferred server technology's documentation for more information on how to allow 404s to be handled on the client with push state.
Dynamically Specify Route Components
You can add a navigationStrategy
to a route to allow dynamic routes. Within the navigation strategy Aurelia requires you to configure instruction.config
with the desired moduleId
, viewPorts
or redirect
.
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
const navStrat = (instruction) => {
instruction.config.moduleId = instruction.fragment
instruction.config.href = instruction.fragment
};
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' },
{ route: ['', 'admin*path'], name: 'route', navigationStrategy: navStrat }
]);
}
}
import {RouterConfiguration, Router, NavigationInstruction} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
const navStrat = (instruction: NavigationInstruction) => {
instruction.config.moduleId = instruction.fragment
instruction.config.href = instruction.fragment
};
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' },
{ route: ['', 'admin*path'], name: 'route', navigationStrategy: navStrat }
]);
}
}
Adding Additional Data To A Route
Although Aurelia does allow you to pass any additional property to a route's configuration object, settings
is the default parameter to which you should add arbitrary data that you want to pass to the route.
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users', settings: {data: '...'} }
]);
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users', settings: {data: '...'} }
]);
}
}
Case Sensitive Routes
You can set a route to be case sensitive, should you wish:
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users', caseSensitive: true }
]);
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users', caseSensitive: true }
]);
}
}
In the above example, our route will only match URL fragment of /users
and not /Users
, but since the route home
is not case sensitive the URL /Home
would match. By default Aurelia's routes are not case sensitive.
Handling Unknown Routes
Aurelia allows you to map any unknown routes. Parameters passed to mapUnknownRoutes()
can be:
- A string to a moduleId. This module will be navigated to any time a route is not found.
- A routeConfig object. This configuration object will be used any time a route is not found.
- A function which is passed the NavigationInstruction object and can decide the route dynamically.
Using a moduleId
for Unknown Routes
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
config.mapUnknownRoutes('not-found');
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
config.mapUnknownRoutes('not-found');
}
}
The above example will make any unmatched routes to load the not-found
component module.
Using A Function For Unknown Routes
The function passed to mapUnknownRoutes()
has to return:
- A string representing
moduleId
. - An object with property
moduleId
of type string. - A
RouteConfig
object. - A
Promise
that resolves to any of the above.
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
const handleUnknownRoutes = (instruction) => {
return { route: 'not-found', moduleId: 'not-found' };
}
config.mapUnknownRoutes(handleUnknownRoutes);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
import {
RouterConfiguration,
NavigationInstruction,
Router,
RouteConfig
} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
const handleUnknownRoutes = (instruction: NavigationInstruction): RouteConfig => {
return { route: 'not-found', moduleId: 'not-found' };
}
config.mapUnknownRoutes(handleUnknownRoutes);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
Redirecting Routes
Aurelia allows redirecting of routes to URL fragments by specifying redirect with a string consisting of a URL fragment.
config.map([
{ route: '', redirect: 'home' },
{ route: 'home', name: 'home', moduleId: 'home/index' }
]);
Use Redirect On Empty Routes with Child Routers
The redirect
is particularly useful when you have an "empty" route pattern (such as the first route above) that maps to a component with a child router. In this case, create a non-empty route and then redirect the empty route to the non-empty route (as above). This will enable the child router to consistently match child routes without getting confused in scenarios where the empty route was matched.
Pipelines
Aurelia has two router classes, AppRouter
and Router
. AppRouter
extends the Router
class and is the main application router. Router
is used for any child routers including nested child routers. One of the main differences between the two is pipelines are only allowed on the AppRouter
and not any child routers.
The default pipeline slots in order are authorize
, preActivate
, preRender
, and postRender
. For each slot, Aurelia has convenience functions for creating a pipeline step for these slots: addAuthorizeStep
, addPreActivateStep
, addPreRenderStep
, addPostRenderStep
. You can create your own pipeline steps using addPipelineStep
, but the step's name must match one of the default pipeline's slots.
authorize
is called between loading the route's step and calling the route view-model'canActivate
function if defined.preActivate
is called between the route view-model'canActivate
function and the previous route view-model'sdeactivate
function if defined.preRender
is called between the route view-model's activate function and before the component is rendered/composed.postRender
is called after the component has been render/composed.
A pipeline step must be an object or a constructor function that resolves to an object that contains a run(navigationInstruction, next)
function.
import {Redirect} from 'aurelia-router';
export class App {
configureRouter(config) {
config.addAuthorizeStep(AuthorizeStep);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', settings: { auth: true } },
{ route: 'users/:id/detail', name: 'userDetail', moduleId: 'users/detail', settings: { auth: true } }
]);
}
}
class AuthorizeStep {
run(navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.settings.auth)) {
var isLoggedIn = // insert magic here;
if (!isLoggedIn) {
return next.cancel(new Redirect('login'));
}
}
return next();
}
}
import {
RouterConfiguration,
NavigationInstruction,
Next,
Redirect
} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
config.addAuthorizeStep(AuthorizeStep);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', settings: { auth: true } },
{ route: 'users/:id/detail', name: 'userDetail', moduleId: 'users/detail', settings: { auth: true } }
]);
}
}
class AuthorizeStep {
run(navigationInstruction: NavigationInstruction, next: Next): Promise<any> {
if (navigationInstruction.getAllInstructions().some(i => i.config.settings.auth)) {
var isLoggedIn = //insert magic here;
if (!isLoggedIn) {
return next.cancel(new Redirect('login'));
}
}
return next();
}
}
export class App {
configureRouter(config) {
function step() {
return step.run;
}
step.run = (navigationInstruction, next) => {
return next();
};
config.addPreActivateStep(step)
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
import {
RouterConfiguration,
NavigationInstruction,
Next
} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
function step() {
return step.run;
}
step.run = (navigationInstruction: NavigationInstruction, next: Next): Promise<any> {
return next();
};
config.addPreActivateStep(step);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
export class App {
configureRouter(config) {
const step = {
run(navigationInstruction, next) {
return next();
}
};
config.addPreRenderStep(step);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
import {RouterConfiguration, NavigationInstruction, Next} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
const step = {
run(navigationInstruction: NavigationInstruction, next: Next): Promise<any> {
return next();
}
};
config.addPreRenderStep(step);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
export class App {
configureRouter(config) {
const step = {
run(navigationInstruction, next) {
return next();
}
};
config.addPostRenderStep(step);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
import {
RouterConfiguration,
NavigationInstruction,
Next
} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
const step = {
run(navigationInstruction: NavigationInstruction, next: Next): Promise<any> {
return next();
}
};
config.addPostRenderStep(step);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
Alternatively, you can also use addPipelineStep(name, step)
with predefined step names from PipelineSlotName
enum export.
import { PipelineSlotName } from 'aurelia-router';
export class App {
configureRouter(config) {
const step = {
run(navigationInstruction, next) {
return next();
}
};
config.addPipelineStep(PipelineSlotName.Authorize, step);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
import {
RouterConfiguration,
NavigationInstruction,
Next,
PipelineSlotName
} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
const step = {
run(navigationInstruction: NavigationInstruction, next: Next): Promise<any> {
return next();
}
};
config.addPipelineStep(PipelineSlotName.Authorize, step);
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
}
}
Router Events
If you want to hook into certain state during while the router is processing a navigation, there are a few events that you can subscribe to:
router:navigation:processing
: fires when the router processes a new navigationrouter:navigation:error
: fires when the router fails to process a navigationrouter:navigation:canceled
: fires when the router navigation process is canceled, i.e whennext.cancel
is invokedrouter:navigation:success
: fires when the router finished processing navigation successfully.router:navigation:complete
: fires when the router finished processing navigation, this always fires regardless navigation processing was canceled, error or successful.
The above event names can be accessed from RouterEvent
enum export:
export const enum RouterEvent {
Processing = 'router:navigation:processing',
Error = 'router:navigation:error',
Canceled = 'router:navigation:canceled',
Complete = 'router:navigation:complete',
Success = 'router:navigation:success',
ChildComplete = 'router:navigation:child:complete'
}
Usage examle:
import { EventAggregator } from 'aurelia-event-aggregator';
import { RouterEvent } from 'aurelia-router';
export class App {
static inject = [EventAggregator];
constructor(ea) {
ea.subscribe(RouterEvent.Complete, (event) => {
console.log(event.instruction);
console.log(event.result.output);
})
}
import { EventAggregator } from 'aurelia-event-aggregator';
import {
NavigationInstruction,
PipelineResult,
RouterEvent
} from 'aurelia-router';
export class App {
static inject = [EventAggregator];
constructor(ea: EventAggregator) {
ea.subscribe(RouterEvent.Complete, (event: { instruction: NavigationInstruction; result: PipelineResult }) => {
console.log(event.instruction);
console.log(event.result.output);
})
}
Rendering View Ports
Every instance of a router-view
custom element essentially defines a "view port". When you give a router-view
a name, you can refer to it in the viewPorts
property of the route configuration in your javascript. The value of a viewPorts
property is an object where each property name is the name of a view port (ie, router-view
) and each value is the moduleId
destination of the route. Thus you can specify any number of view ports on a single route configuration.
If you don't name a router-view
, it will be available under the name 'default'.
Following is an example of the use of view ports:
<template>
<div>
<router-view name="left"></router-view>
</div>
<div>
<router-view name="right"></router-view>
</div>
</template>
export class App {
configureRouter(config) {
config.title = 'Aurelia';
config.map([
{ route: 'users', name: 'users', viewPorts: { left: { moduleId: 'user/list' }, right: { moduleId: 'user/detail' } } }
]);
}
}
import {RouterConfiguration} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
config.title = 'Aurelia';
config.map([
{ route: 'users', name: 'users', viewPorts: { left: { moduleId: 'user/list' }, right: { moduleId: 'user/detail' } } }
]);
}
}
Empty View Ports
You can empty a view port by setting moduleId
null in the route configuration for that view port.
export class App {
configureRouter(config) {
config.title = 'Aurelia';
config.map([
{ route: 'users', name: 'users', viewPorts: { left: { moduleId: 'user/list' }, right: { moduleId: null } } }
]);
}
}
import {RouterConfiguration} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
config.title = 'Aurelia';
config.map([
{ route: 'users', name: 'users', viewPorts: { left: { moduleId: 'user/list' }, right: { moduleId: null } } }
]);
}
}
View Port Defaults
The empty view port is actually the out-of-the-box default. You can override this default to load a specific moduleId
whenever moduleId
is null by passing a view port configuration to the router configuration. These overrides can be set specifically to each view port.
export class App {
configureRouter(config) {
config.title = 'Aurelia';
config.map([
{ route: 'users', name: 'users', viewPorts: { left: { moduleId: 'user/list' }, right: { moduleId: null } } }
]);
config.useViewPortDefaults({
right: { moduleId: 'pages/placeholder' }
})
}
}
import {RouterConfiguration} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
config.title = 'Aurelia';
config.map([
{ route: 'users', name: 'users', viewPorts: { left: { moduleId: 'user/list' }, right: { moduleId: null } } }
]);
config.useViewPortDefaults({
right: { moduleId: 'pages/placeholder' }
})
}
}
Optional View Ports
If a view port configuration is not defined on a route, the router will skip routing on that particular view port leaving the view port untouched. If there is no existing content in the view port, i.e. when the application is first loaded, the view port will be populated with the view port default configuration, which is empty if not otherwise specified (see View Port Defaults).
export class App {
configureRouter(config) {
config.title = 'Aurelia';
config.map([
{ route: 'users', name: 'users', viewPorts: { left: { moduleId: 'user/list' } } }
]);
config.useViewPortDefaults({
right: { moduleId: 'pages/placeholder' }
})
}
}
import {RouterConfiguration} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration): void {
config.title = 'Aurelia';
config.map([
{ route: 'users', name: 'users', viewPorts: { left: { moduleId: 'user/list' } } }
]);
config.useViewPortDefaults({
right: { moduleId: 'pages/placeholder' }
})
}
}
In addition to the moduleId
, you can also specify a "layout" in the configuration of a view port (see
Layouts
).
Layouts
Similar to MVC-style master/layout pages, Aurelia allows you to use a "layout" view like an MVC "master template" for a set of views.
The set of views subject to being part of a layout is defined in Aurelia as a set of views referenced by one or more routes in a router configuration. There are two ways to associate a layout with routes. The first is via HTML, the second is via view model code.
We're going to be a little sloppy here in terminology. Technically, routes refer to "moduleIds", not
"views". Since the router resolves a moduleId
to a view, indirectly the router does reference a view. It is easy to picture a view visually contained within a layout, so in this topic to we'll refer to views referenced by a route, not modules.
We'll look at using HTML first. We know that the router-view
custom HTML element is always associated with a set of one or more views referenced in a router configuration given in its parent view's view model. By associating a layout with a router-view
one can thus associate a layout with the same set of views with which the router-view
is associated.
To specify a layout on the router-view
custom element, we use the following attributes:
layout-view
- specifies the file name (with path) of the layout view to use.layout-view-model
- specifies the moduleId of the view model to use with the layout view.layout-model
- specifies the model parameter to pass to the layout view model'sactivate
function.
All of these layout attributes are bindable.
Following is an example of HTML in which we specify that we want all destination views reachable under the router-view
to be laid-out inside a view with file name layout.html
, located in the same directory as the view contianing the router-view
:
<template>
<div>
<router-view layout-view="layout.html"></router-view>
</div>
</template>
Here is the layout view itself:
<template>
<div class="left-content">
<slot name="left-content"></slot>
</div>
<div class="right-content">
<slot name="right-content"></slot>
</div>
</template>
And here we define a view that we want to appear within the layout:
<template>
<div slot="left-content">
<p>${leftMessage}.</p>
</div>
<div slot="right-content">
<p>${rightMessage}.</p>
</div>
<div>This will not be displayed in the layout because it is not contained in any named slot referenced by the layout.</div>
</template>
export class Home {
constructor() {
this.leftMessage = "I'm content that will show up on the left";
this.rightMessage = "I'm content that will show up on the right";
}
}
export class Home {
leftMessage: string;
rightMessage: string;
constructor() {
this.leftMessage = "I'm content that will show up on the left";
this.rightMessage = "I'm content that will show up on the right";
}
}
Observe how we use the slot
mechanism for associating parts of the layout to parts of the views that are to be contained within the layout. (Happy for developers, this is conveniently the same mechanism and syntax we use in Aurelia when providing content to custom elements.)
Now we just have to define the route configuration that will be associated with the router-view
:
export class App {
configureRouter(config, router) {
config.map([
{ route: '', name: 'home', moduleId: 'home' }
]);
this.router = router;
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
config.map([
{ route: '', name: 'home', moduleId: 'home' }
]);
this.router = router;
}
}
Thus when we navigate to the module "home" we find that it is laid-out as desired inside the layout view.
Note there is nothing different about the above route configuration with or without the layout. It may reference any number of views that would all be included by default in the layout.
So that is how we use HTML to associate a layout view with a set of views referenced in a router configuration.
We can also associate layouts with route configurations using code in our view model. Suppose we like what we've done above, but we have a couple views that we would like to associate with a different layout and would thus like to partially override the configuration given in the HTML. The following code is an example of how we can do that:
export class App {
configureRouter(config, router) {
config.map([
{ route: '', name: 'home', moduleId: 'home' },
{ route: 'login', name: 'login', moduleId: 'login/index', layoutView: 'layout-login.html' },
{ route: 'users', name: 'users', moduleId: 'users/index', layoutViewModel: 'layout-users', layoutModel: { access: "admin" } }
]);
this.router = router;
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration, router: Router): void {
config.map([
{ route: '', name: 'home', moduleId: 'home' },
{ route: 'login', name: 'login', moduleId: 'login/index', layoutView: 'layout-login.html' },
{ route: 'users', name: 'users', moduleId: 'users/index', layoutViewModel: 'layout-users', layoutModel: { access: "admin" } }
]);
this.router = router;
}
}
The above example will assign different layouts to the "login" and "users" views, overriding the HTML while leaving "home" to remain as configured in the HTML. Noticing we're using camel-cased property names here, unlike in the HTML.
You can also specify a layout in the viewPorts
configuration of a route. See a simple example, below:
<template>
<div>
<router-view name="myRouterView"></router-view>
</div>
</template>
export class App {
configureRouter(config, router) {
config.map([
{ route: '', name: 'home', viewPorts: { myRouterView: { moduleId: 'home', layoutView: 'default.html' } } }
]);
this.router = router;
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {){
config.map([
{ route: '', name: 'home', viewPorts: { myRouterView: { moduleId: 'home', layoutView: 'default.html' } } }
]);
this.router = router;
}
}
View Swapping and Animation
When the Aurelia router navigates from one view to another, we refer to this as "swapping" one view for another. Aurelia gives us an optional set of strategies dictating how a swap proceeds, or more specifically, how animation plays out during the swap. We refer to these strategies more precisely as the "swap order".
If there is no animation defined, then swap-order has no visible impact.
You can apply a swap strategy to one or more routes by applying the swap-order
attribute to a router-view
custom HTML element. The strategy will then be applied in any transition between two views accessible under the router-view
.
swap-order
is bindable.
The following swap order strategies are available:
- before - animate the next view in before removing the current view
- with - animate the next view at the same time the current view is removed
- after - animate the next view in after the current view has been removed (the default)
Here is an example of setting the swap order strategy on a router-view
:
<template>
<div>
<router-view swap-order="before"></router-view>
</div>
</template>
Internationalizing Titles
If your application targets multiple cultures or languages, you probably want to translate your route titles. The Router
class has a transformTitle
property that can be used for this. It is expected to be assigned a function that takes the active route's title as a parameter and then returns the translated title. For example, if your app uses aurelia-i18n
, its routes' titles would typically be set to some translation keys
and the AppRouter
's transformTitle
would be configured in such a way that the active route's title is translated using the I18N
's tr
method. Additionally you can listen to a custom event published by the I18N service to react on locale changes using the EventAggregator:
import Backend from 'i18next-xhr-backend';
import {AppRouter} from 'aurelia-router';
import {EventAggregator} from 'aurelia-event-aggregator';
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.plugin('aurelia-i18n', i18n => {
i18n.i18next.use(Backend);
return i18n.setup({
backend: {
loadPath: './locales/{{lng}}.json',
},
lng : 'en',
fallbackLng : 'en'
}).then(() => {
const router = aurelia.container.get(AppRouter);
router.transformTitle = title => i18n.tr(title);
const eventAggregator = aurelia.container.get(EventAggregator);
eventAggregator.subscribe('i18n:locale:changed', () => {
router.updateTitle();
});
});
});
aurelia.start().then(() => aurelia.setRoot());
}
import Backend from 'i18next-xhr-backend';
import {Aurelia} from 'aurelia-framework';
import {AppRouter} from 'aurelia-router';
import {EventAggregator} from 'aurelia-event-aggregator';
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.plugin('aurelia-i18n', i18n => {
i18n.i18next.use(Backend);
return i18n.setup({
backend: {
loadPath: './locales/{{lng}}.json',
},
lng : 'en',
fallbackLng : 'en'
}).then(() => {
const router = aurelia.container.get(AppRouter);
router.transformTitle = title => i18n.tr(title);
const eventAggregator = aurelia.container.get(EventAggregator);
eventAggregator.subscribe('i18n:locale:changed', () => {
router.updateTitle();
});
});
});
aurelia.start().then(() => aurelia.setRoot());
}
{
"titles": {
"app": "My App",
"home": "Home"
}
}
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'titles.app';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home', title: 'titles.home' }
]);
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'titles.app';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home', title: 'titles.home' }
]);
}
}
The default value of thetransformTitle
property does the following:
- For the child
Router
, it delegates to its parent'stransformTitle
function. - For the
AppRouter
, it returns the title untransformed.
In the previous example, the AppRouter
's transformTitle
is set, so all child Router
s will delegate down to it by default. However, this means that the transformTitle
can be overridden for specific child Router
s if some areas of your app need custom transformation.
Configuring a Fallback Route
Whenever navigation is rejected, it is redirected to a previous location. However in certain cases a previous location doesn't exist, e.g. when it happens as the first navigation after the startup of application. To handle this scenario, you can set up a fallback route.
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
config.fallbackRoute('users');
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' }
]);
config.fallbackRoute('users');
}
}
Reusing an Existing View Model
Since the view model's navigation lifecycle is called only once, you may have problems recognizing that the user switched the route from Product A
to Product B
(see below). To work around this issue implement the method determineActivationStrategy
in your view model and return hints for the router about what you'd like to happen. Available return values are replace
and invoke-lifecycle
. Remember, "lifecycle" refers to the navigation lifecycle.
//app.js
export class App {
configureRouter(config) {
config.title = 'Aurelia';
config.map([
{ route: 'product/a', moduleId: 'product', nav: true },
{ route: 'product/b', moduleId: 'product', nav: true },
]);
}
}
//product.js
import {activationStrategy} from 'aurelia-router';
export class Product {
determineActivationStrategy() {
return activationStrategy.replace;
}
}
import {RouterConfiguration} from 'aurelia-router';
//app.ts
export class App {
configureRouter(config: RouterConfiguration): void {
config.title = 'Aurelia';
config.map([
{ route: 'product/a', moduleId: 'product', nav: true },
{ route: 'product/b', moduleId: 'product', nav: true },
]);
}
}
//product.ts
import {activationStrategy, RoutableComponentDetermineActivationStrategy} from 'aurelia-router';
export class Product implements RoutableComponentDetermineActivationStrategy {
determineActivationStrategy() {
return activationStrategy.replace;
}
}
Alternatively, if the strategy is always the same and you don't want that to be in your view model code, you can add the activationStrategy
property to your route config instead.