Learn how to work with the @aurelia/router package to implement routing in your Aurelia applications.
Routing with Aurelia feels like a natural part of the framework. It can easily be implemented into your applications in a way that feels familiar if you have worked with other frameworks and library routers.
This section is broken up into two parts—a quick introduction to the router and router configuration.
If you are looking for details on configuring the router (set titles, handle unknown routes, etc.), please see the Configuration section at the end of this guide.
Currently, two routers ship with Aurelia: router lite and core router. This section refers to the core router package that lives in @aurelia/router
— please see the warning note below on a caveat some developers encounter when working with the router.
Before you go any further: Please ensure you are importing from the @aurelia/router
package. Sometimes import extensions will autocomplete your imports and import from the aurelia
package, which currently exports the lite router. Eventually, the aurelia
package will export the @aurelia/router
package, but it currently does not. We have noticed, in many instances, that using the incorrect router imports is why routing is not working.
See how you can configure and implement routing in your Aurelia applications in only a few minutes. Of course, you will want to expand upon this as you build your routes. Here we only learn the bare minimum to get started.
The following getting started guide assumes you have an Aurelia application already created. If not, consult our Quick Start to get Aurelia installed in minutes.
To use the router, we have to register it with Aurelia. We do this inside of main.ts
(or main.js
if you're working with Javascript) — the router is then enabled after it is registered. You might already have code like this if you chose the routing example when generating using the Makes scaffolding tool.
Once again, it bears repeating. Please make sure your router imports are being imported from @aurelia/router in your `main.ts` file, but also in other parts of your Aurelia application as well.
Now, we create our routes. We'll do this inside my-app.ts
and use the static routes
property. Please note that there is also a @routes
decorator, which is detailed inside the Creating Routes section.
For our two routes, we import and provide their respective components. Your components are just classes and can be very simple. Here is the HomePage
component. Please note that you can use inline imports when creating routes, also detailed in the Creating Routes section.
Take note of the path
property which is empty. This tells the router that the HomePage
component is our default route. If no route is supplied, it will load this as the default component. The component
property is the component that will be loaded (self-explanatory). And the title
property is the title for our route.
And the view model for our component is equally simple:
First, let's look at the HTML. If you use the makes
tool to scaffold your Aurelia application. This might be my-app.html
load
Notice how we use a standard hyperlink <a>
tags, but they have an load
attribute instead of href
? This attribute tells the router that these are routable links. The router will translate these load
values into routes (either path or route name). By default, the router does also allow you to use href
for routes (a setting that can be turned off below configuring useHref
).
au-viewport
This tells the router where to display your components. It can go anywhere inside your HTML. It can also have a name (handy for instances where there are multiple au-viewport
elements), and you can have more than one.
The router allows you to configure how it interprets and handles routing in your Aurelia applications. The customize
method on the RouterConfiguration
object can be used to set numerous router settings besides the useUrlFragmentHash
value.
Can't find what you're looking for in this section? We have a Router Recipes section detailing many tasks for working with the router, from passing data between routes to route guards.
The title can be set for the overall application. By default, the title uses the following value: ${componentTitles}${appTitleSeparator}Aurelia
the component title (taken from the route or component) and the separator, followed by Aurelia.
In most instances, using the above string title is what you will want. You will want the solution below if you need to set the title or transform the title programmatically.
Using the transformTitle
method from the router customization, the default title-building logic can be overwritten. This allows you to set the title programmatically, perform translation (using Aurelia i18n or other packages) and more.
Are you trying to set the title using the Aurelia i18n package? Visit the section on configuring translated router titles here.
If you do not provide any configuration value, the default is hash-based routing. This means a hash will be used in the URL. If your application requires SEO-friendly links instead of hash-based routing links, you will want to use pushState.
We are performing the configuration inside of the main.ts
file, which is the default file created when using the Makes
CLI tool.
By calling the customize
method, you can supply a configuration object containing the property useUrlFragmentHash
and supplying a boolean value. If you supply true
this will enable hash mode. The default is true
.
If you are working with pushState routing, you will need a base HREF value in the head of your document. The scaffolded application from the CLI includes this in the index.html
file, but if you're starting from scratch or building within an existing application, you need to be aware of this.
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 your preferred server technology's documentation for more information on how to allow 404s to be handled on the client with push state.
The useHref
configuration setting is something all developers working with routing in Aurelia need to be aware of. By default, the router will allow you to use both href
as well as load
for specifying routes.
Where this can get you into trouble are external links, mailto links and other types of links that do not route. A simple example looks like this:
By default, this seemingly innocent and common scenario will trigger the router and cause an error in the console.
You have two options when it comes to working with external links. You can specify the link as external using the external
attribute.
Or, you can set useHref
to false
and only ever use the load
attribute for routes.
If you are using the router to render components in your application, there might be situations where a component attempts to be rendered that do not exist. This can happen while using direct routing (not configured routing)
This section is not for catch-all/404 routes. If you are using configured routing, you are looking for the section on catch-all routes here.
To add in fallback behavior, we can do this in two ways. The fallback
attribute on the <au-viewport>
element or in the router customize
method (code).
Let's create the missing-page
component (this is required, or the fallback behavior will not work). First, we'll create the view model for our missing-page
component.
For the fallback
component, an ID gets passed as a parameter which is the value from the URL. If you were to attempt to visit a non-existent route called "ROB," the missingComponent
value would be ROB.
Now, the HTML.
By using the fallback
property on the customize
method when we register the router, we can pass a component.
Sometimes the fallback
attribute can be the preferred approach to registering a fallback. Import your fallback component and pass the name to the fallback
attribute. The same result, but it doesn't require touching the router registration.
The swapStrategy
configuration value determines how contents are swapped in a viewport when transitioning. Sometimes, you might want to change this depending on the type of data you are working with or how your routes are loaded. A good example of configuring the swap order is when you're working with animations.
attach-next-detach-current
(default)
attach-detach-simultaneously
detach-current-attach-next
detach-attach-simultaneously
Still, confused or need an example? You can find an example application with routing over on GitHub here.
Learn all there is to know about creating routes in Aurelia.
The router takes your routing instructions and matches the URL to determine what components to render. When the URL patch matches the configured route path, the component is loaded in the case of configured routes.
To register routes, you can either use the @route
decorator or the static routes property static routes
to register one or more routes in your application.
The routing syntax used in the Aurelia router is similar to that of other routers you might have worked with before. The syntax will be very familiar if you have worked with Express.js routing.
A route is an object containing a few required properties that tell the router what component to render, what URL it should match and other route-specific configuration options.
At a minimum, a route must contain path
and component
properties, or path
and redirectTo
properties. The component
and redirectTo
properties can be used in place of one another, allowing you to create routes that point to other routes.
The path
property on a route is where you'll spend the most time configuring your routes. The path tells the router what to match in the URL, what parameters there are and if they're required.
A path can be made up of either a static string with no additional values or an array of strings. An empty path value is interpreted as the default route, and only one should be specified.
Parameters are supplied to canLoad
and loading
router lifecycle callbacks as the first argument. They are passed as an object with key/value pairs. Please consult the Routing Lifecycle section to learn how to access them.
Named required parameters that are prefixed with a colon. :productId
when used in a path, a named required parameter might look like this:
This named parameter is denoted by the colon prefix and is called productId
which we will be able to access within our routed component.
Named optional parameters. Like required parameters, they are prefixed with a colon but end with a question mark.
In the above example, we have an optional parameter called variation
. We know it's optional because of the question mark at the end. This means it would still be valid if you visited this route with supplying the variation parameter.
Using optional name parameters is convenient for routes where different things can happen depending on the presence of those optional parameters.
Wildcard parameters. Unlike required and optional parameters, wildcard parameters are not prefixed with a colon, instead using an asterisk. The asterisk works as a catch-all, capturing everything provided after it.
In the above code example, we can have an endless path after which it is supplied as a value to the canLoad
and load
methods.
Besides the basics of path
and component
a route can have additional configuration options.
id
— The unique ID for this route
redirectTo
— Allows you to specify whether this route redirects to another route. If the redirectTo
path starts with /
it is considered absolute, otherwise relative to the parent path.
caseSensitive
— Determines whether the path
should be case sensitive. By default, this is false
transitionPlan
— How to behave when this component is scheduled to be loaded again in the same viewport. Valid values for transitionPlan are:
replace
— completely removes the current component and creates a new one, behaving as if the component changed.
invoke-lifecycles
— calls canUnload
, canLoad
, unload
and load
(default if only the parameters have changed)
none
— does nothing (default if nothing has changed for the viewport)
title
— Specify a title for the route. This can be a string, or it can be a function that returns a string.
viewport
— The name of the viewport this component should be loaded into.
data
— Any custom data that should be accessible to matched components or hooks. This is where you can specify data such as roles and other permissions.
routes
— The child routes that can be navigated from this route.
By specifying the redirectTo
property on our route, we can create route aliases. These allow us to redirect to other routes. We redirect our default route to the products page in the following example.
When creating routes, it is important to note that the component
property can do more than accept inline import statements. You can also import the component and specify the component class as the component property if you prefer.
If you are working with the Aurelia application generated using npx makes aurelia
you would already have a my-app.ts
file to place your routes in. It's the main component of the scaffolded Aurelia application.
As you will learn towards the end of this section, inline import statements allow you to implement lazy-loaded routes (which might be needed as your application grows in size).
If you have a lot of routes, the static property might be preferable from a cleanliness perspective.
If you have more than a few routes, it might be best practice to write them in a separate file and then import them inside your application.
The syntax for routes stays the same using the decorator. Just how they have defined changes slightly.
As your application grows, child routes can become a valuable way to organize your routes and keep things manageable. Instead of defining all your routes top-level, you can create routes inside your child components to keep them contained.
An example of where child routes might be useful in creating a dashboard area for authenticated users.
We add a route in our top-level my-app.ts
component where we added routes in our previous examples. Now, we will create the dashboard-page component.
You will notice we create routes the same way we learned further above. However, we are defining these inside a component we use for our dashboard section. Notice how we use the au-viewport
element inside of the dashboard-page
component.
Lastly, let's create our default dashboard component for the landing page.
Now, we can contain all dashboard-specific routes inside of our dashboard-page
component for dashboard views. Furthermore, it allows us to implement route guards to prevent unauthorized users from visiting the dashboard.
When a user attempts to visit a route that does not exist, we want to catch this route attempt using a catch-all route. We can use a wildcard *
to create a route that does this.
When using a catch-all wildcard route, ensure that it is the last route in your routes array, so it does not hijack any other valid route first.
A good use of a catch-all route might be to redirect users away to a landing page. For example, if you had an online store, you might redirect users to a products page.
You can also specify a component that gets loaded like a normal route:
Most modern bundlers like Webpack support lazy bundling and loading of Javascript code. The Aurelia router allows you to create routes that are lazily loaded only when they are evaluated. What this allows us to do is keep the initial page load bundle size down, only loading code when it is needed.
By specifying an arrow function that returns an inline import
we are telling the bundler that our route is to be lazily loaded when requested.
Inline import statements are a relatively new feature. Inside your tsconfig.json file, ensure your module property is set to esnext to support inline import statements using this syntax.
We went over creating routes with support for parameters in the creating routes section, but there is an additional property you can specify on a route called data,
, which allows you to associate metadata with a route.
This data property will be available in the routable component and can be a great place to store data associated with a route, such as roles and auth permissions. In some instances, the route parameters can be used to pass data, but for other use cases, you should use the data property.
A common scenario is styling an active router link with styling to signify that the link is active, such as making the text bold. When a route is active, by default, a CSS class name of active
will be added to the route element.
In your HTML, if you were to create some links with load
attributes and visit one of those routes, the active
class would be applied to the link for styling. In the following example, visiting the about route would put class="active"
onto our a
element.
The routing lifecycle allows you to run code at different points of the routing lifecycle such as fetching data or changing the UI.
Inside your routable components which implement the IRouteableComponent
interface, certain methods are called at different points of the routing lifecycle. These lifecycle hooks allow you to run code inside of your components, such as fetching data or changing the UI itself.
Router lifecycle hook methods are all completely optional. You only have to implement the methods you require. The router will only call a method if it has been specified inside of your routable component. All lifecycle hook methods also support returning a promise and can be asynchronous.
If you are working with components you are rendering, implementing IRouteableComponent
will ensure that your code editor provides you with intellisense to make working with these lifecycle hooks easier.
The canLoad
method is called upon attempting to load the component. If your route has any parameters supplied, they will be provided to the canLoad
method as an object with one or more parameters as the first argument.
If you were loading data from an API based on values provided in the URL, you would most likely do that inside canLoad
if the view is dependent on the data successfully loading.
The canLoad
method allows you to determine if the component should be loaded or not. If your component relies on data being present from the API or other requirements being fulfilled before being allowed to render, this is the method you would use.
When working with the canLoad
method, you can use promises to delay loading the view until a promise and/or promises have been resolved. The component would be loaded if we were to return true
from this method.
If you wanted to load data from an API, you could make the canLoad
method async, which would make it a promise-based method. You would be awaiting an actual API call of some kind in place of ....load data
Unlike other async methods, if the promise does not resolve, the component will not load. The canLoad
lifecycle method tells the router if the component is allowed to load. It's a great router method for components that rely on data loading such as product detail or user profile pages.
Not only can we allow or disallow the component to be loaded, but we can also redirect it. If you want to redirect to the root route, return a string with an /
inside it. You can return a route ID, route path match or navigation instruction from inside this callback to redirect.
Returning a boolean false, string or RoutingInstruction from within the canLoad
function will cancel the router navigation.
The loading
method is called when your component is navigated to. If your route has any parameters supplied, they will be provided to the load
method as an object with one or more parameters as the first argument.
If you are loading data from an API based on values provided in the URL and the rendering of this view is not dependent on the data being successfully returned, you can do that inside of load
.
In many ways, the loading
method is the same as canLoad
with the exception that loading
cannot prevent the component from loading. Where canLoad
can be used to redirect users away from the component, the loading
method cannot.
All of the above code examples for canLoad
can be used with loading
and will work the same, with exception of being able to return true
or false
boolean values to prevent the component being loaded (as we just mentioned).
The canUnload
method is called when a user attempts to leave a routed view. The first argument of this callback is a INavigatorInstruction
it provides information about the next route. You can return a component, boolean or string value from this method.
Like the canLoad
method, this is just the inverse. It determines if we can navigate away from the current component.
The unloading
method is called if the user is allowed to leave and is in the process of leaving. The first argument of this callback is a INavigatorInstruction
it provides information about the next route.
Like the loading
method, this is just the inverse. It is called when the component is unloaded (provided canUnload
wasn't false).
A common router scenario is you want to route to a specific component, say a component that displays product information based on the ID in the URL. You request the API to get the information and display it.
Two asynchronous lifecycles are perfect for dealing with loading data: canLoad
and load
- both supporting returning a promise (or async/await).
If the component you are loading absolutely requires the data to exist on the server and be returned, the canLoad
lifecycle method is the best place to do it. Using our example of a product page, if you couldn't load product information, the page would be useful, right?
From the inside canLoad
you can redirect the user elsewhere or return false to throw an error.
Similarly, if you still want the view to load, even if we can't get the data, you would use the loading
lifecycle callback.
When you use load
and async
the component will wait for the data to load before rendering.
If you worked with routing in Aurelia 1, you might be accustomed to a currentInstruction
property available on the router. In Aurelia 2, this property does not exist. There are, however, two properties on the router called activeNavigation
and activeComponents
which can be used to achieve a similar result. You can also reference the instruction itself from within route lifecycle functions.
The activeNavigation
property contains quite a few properties but most notably has the current URL path, query parameters and other navigation-specific values. You might want to get information about the current route.
We can get information about the current route by accessing the activeComponents
array and determining the active component. Still, it is possible that more than one component will be in this array. An easier way is to get the route instruction on the canLoad
and loading
lifecycle methods.
It might seem like a mouthful, but to get the current instruction that resulted in the viewport's current content, this is the current approach to take from within those aforementioned methods inside your components.
The parameters
object contains a Javascript object of any URL parameters. If your URL contains /?myprop=22&frag=0
then this object would contain {myprop: '22', frag: '0'}
, allowing you to get fragment values.
While you would often set the title of a route in your route configuration object using the title
property, sometimes you want the ability to specify the title property from within the routed component itself.
You can achieve this from within the canLoad
and load
methods in your component. By setting the next.title
property, you can override or transform the title.
The <au-viewport>
element is where all of the routing magic happens, the outlet. It supports a few different custom attributes, allowing you to configure how the router renders your components. It also allows you to use multiple viewports to create different layout configurations with your routing.
The router allows you to add multiple viewports to your application and render components into each viewport element by their name. The <au-viewport>
element supports a name attribute, which you'll want to use if you have more than one.
In this example, we have the main viewport for our main content, and another viewport called sidebar
for our sidebar content which is dynamically rendered. When using viewports, think of them like iframes, independent containers that can maintain their own states.
Routes will load in the default viewport element if there are one or more viewports. However, routes can be told to load into a specific viewport.
By specifying the viewport
property on a route, we can tell it to load into a specific route.
Learn how to navigate the router programmatically using the router load method and the HTML load attribute for creating in-page routes.
This section details how you can use the load method on the router instance or load attribute to navigate to other parts of your application.
To use the load
method, you have first to inject the router into your component. This can be done easily by using the IRouter
decorator on your component constructor method. The following code will add a property to your component, which we can reference.
The load
method can accept a simple string value allowing you to navigate to another component without needing to supply configuration options.
You could also use the string value method to pass parameter values and do something like this where our route expects a product ID, and we pass 12:
The router instance load
method allows you to specify different properties on a per-use basis. The most common one is the title
property, which allows you to modify the title as you navigate your route.
A list of available load options can be found below:
title
— Sets the title of the component being loaded
parameters
— Specify an object to be serialized to a query string and then set to the query string of the new URL.
fragment
— Specify the hash fragment for the new URL.
These option values can be specified as follows and when needed:
The router also allows you to decorate links and buttons in your application using a load
attribute, which works the same way as the router instance load
method.
If you have routes defined on a root level (inside of my-app.ts
) you will need to add a forward slash in front of any routes you attempt to load. The following would work in the case of an application using configured routes.
The load attribute can do more than accept a string value. You can also bind to the load attribute for more explicit routing. The following example is a bit redundant as specifying route:product
would be the same as specifying load="product"
, but if you're wanting more explicit routing, it conveys the intent better.
And where things start to get interesting is when you want to pass parameters to a route. We use the params
configuration property to specify parameters.
In the above example, we provide the route (id
) value (via route: profile
). But, then also provide an object of parameters (via params.bind: { name: 'rob' }
).
These parameter values correspond to any parameters configured in your route definition. In our case, our route looks like this:
Depending on the scenario, you will want to redirect users in your application. Unlike using the load
API on the router where we manually route (for example, after logging in) redirection allows us to redirect inside router hooks.
Please see the Routing Lifecycle section to learn how to implement redirection inside your components.
How to implement router "guards" into your applications to protect routes from direct access.
You might know router hooks as guards in other routers. Their role is to determine how components are loaded. They're pieces of code that are run in between.
The lifecycle hooks sharing API can be used to define reusable hook logic. In principle, nothing new needs to be learned: their behavior is the same as described in Lifecycle Hooks, with the only difference being that the view model instance is added as the first parameter.
If you worked with Aurelia 1, you might know these by their previous name: router pipelines.
Shared lifecycle hook logic can be defined by implementing a router lifecycle hook on a class with the @lifecycleHooks()
decorator. This hook will be invoked for each component where this class is available as a dependency. This can be either via a global registration or via one or more component-local registrations, similar to how, e.g. custom elements and value converters are registered.
In the example above, we register NoopAuthHandler
globally, which means it will be invoked for each routed component and return true
each time, effectively changing nothing.
Please note that you are not recommended to use global lifecycle hooks when you can avoid them, as they are run for each component, the same as you would use inside.
Because lifecycle hooks are invoked for each component, it is considered best practice to ensure that you name your lifecycle hooks appropriately, especially if you're working in a team where developers might not be aware of hooks modifying global component lifecycle behaviors.
While lifecycle hooks are indeed their own thing independent of the components you are routing to, the functions are basically the same as you would use inside an ordinary component.
This is the contract for ordinary route lifecycle hooks for components:
And this is the contract for shared lifecycle hooks
The only difference is the addition of the first viewModel
parameter. This comes in handy when you need the component instance since the this
keyword won't give you access like in ordinary component methods.
When dealing with route hooks, you might only want to apply those to specific components. Imagine an authentication workflow where you would want to allow unauthenticated users to access your login or contact page.
To do this, we can specify our route hook as a dependency in the component via the static dependencies
property, which takes an array of one or more dependencies.
Whenever someone tries to route to the SettingsPage
component, they will trigger the authentication hook you created. This per-component approach allows you to target the needed components you want behind a route hook.
Shared lifecycle hooks run in parallel with (but are started before) component instance hooks, and multiple of the same kind can be applied per component. When multiple hooks are registered per component, they are invoked in the registration order.
It is also permitted to define more than one hook per shared hook class:
Strategies for stateful router animation
A common scenario in a single-page application is page transitions. When a page loads or unloads, an animation or transition effect might be used to make it feel more interactive and app-like.
By leveraging lifecycle hooks, we can perform animations and transition effects in code.
At first glance, this might look like a lot of code, but we do the animation inside of the attaching
and detaching
hooks. Using the Anime.js animation library, we create two animation functions for sliding our views in and out.
We use the created
lifecycle callback to access the host element (the outer element of our custom element) which we will animate. Most of the other callbacks determine the direction we are heading in.
We inject out AnimationHooks
class into our main component, but we also inject it into the sub-components we want to animate. We avoid setting our hook globally, or it would run for all components (which you might want).
As you can see, besides some router-specific lifecycle methods, animating with the router isn't router-specific and leverages Aurelia lifecycles.
A link to a demo of slide in and out animations based on routing can be seen below:
The router emits several events via the Event Aggregator, allowing you to listen to router events. In some situations, you might opt for a router hook, but in other cases, an event might be what you are after.
A good example of where using events might be more appropriate is showing and hiding loaders and other parts of your applications related to routing.
The events fired are:
au:router:router-start
au:router:router-stop
au:router:navigation-start
au:router:navigation-end
au:router:navigation-cancel
au:router:navigation-error
To listen to these events, you subscribe to them using the event aggregator like this:
As you might expect, these events are named in an intuitive way depending on the action taking place inside the router.
You will want to listen to the end, cancel and error navigation events if you're relying on displaying and hiding parts of the UI based on the router to ensure you're checking for a true "done" state.
Aurelia comes with a powerful fully-featured router without the need to install any additional dependencies. If you are new to Aurelia, we recommend visiting the Getting Started section first to familiarise you with the framework.
You are here because you want to familiarize yourself with the router, so we'll make this quick. At the end of this tutorial, you will be familiar with all the concepts and APIs you need to add routing into your Aurelia applications.
While building our recipe application, we will cover the following:
How to create and configure routes
Navigating with load
in your views as well as view-models
Styling active links
Programmatically navigating using router APIs
Working with route parameters
A working demo and code for the following tutorial can also be found here.
To do this tutorial, you'll need a working Aurelia application. We highly recommend following the Quick Start guide to scaffold an application. However, for this tutorial, we have a starter Aurelia 2 application ready to go that we recommend. It will allow you to follow along and live code.
As you will see, it's a boring application with no routing or anything exciting. All it says is Recipes (hardly a recipe application).
The viewport is where our loaded routes are dynamically loaded into. When we click on a route, the <au-viewport>
element will be populated with the requested component.
Open my-app.html
and add in the <au-viewport>
and some navigation links.
This won't do anything just yet because we haven't enabled routing or created any routes.
Let's go into main.ts
and configure Aurelia to use routing. Import the RouterConfiguration
object and pass it to Aurelia's register
method.
We also configure the router to use push-state routing instead of the hash-based router. This gives us cleaner URLs.
Now that we have a viewport and the router enabled let's create some routes. We will start by adding our routes to our root my-app.ts
component.
The important thing to note here is that we are not specifying a component to load for these routes. We will do that shortly, but the routing structure is what we are creating here.
The first route has an empty path
, which means it's a default route (the router will load this first if no other route is requested). The second route recipes
tells the router when the user visits /recipes
to load our recipes component. Lastly, the recipes/:recipeId
route has a route parameter that allows us to load specific recipes.
We now need to create three components for our routes: the homepage, recipes page, and recipe detail page.
Unlike other frameworks and libraries, Aurelia works on the premise of a view-and-view model. If you familiarize yourself with the Getting Started section, you would already be familiar with these concepts.
Let's create the homepage first:
Let's create the recipes page:
Let's create the recipe detail page:
Lastly, let's import our components in my-app.ts
for the routes to load them.
In a non-tutorial application, you would have your API and server providing this data. But, for our tutorial will use a public recipe API instead. We could use mock data, but it would be a lot of data for a recipe application.
The MealDB is a fantastic free meal API that can give us recipes. We will use the Aurelia Fetch Client to get this data, which wraps up the native Fetch API.
In recipes-page.ts
add the following to the component view model:
We've loaded the recipes. Now it's time to display them. Open recipes-page.html
and add in the following:
We use Aurelia's repeat.for
functionality to loop over the recipes. But take notice of the link with load
attribute. Aurelia Router sees this and knows this is a route. We are providing the recipe detail route with the ID of the recipe.
It's not pretty, but we now have a list of recipes.
Sometimes a user might attempt to visit a recipe or page that doesn't exist. You might want to redirect the user or display a 404 page in those situations.
Let's create another component and call it fourohfour-page
We then specify on our <au-viewport>
what our fallback is. We need to import this 404 component to use it.
When we created our routes in my-app.ts
you might recall we created a recipe detail route which had a recipeId
parameter. Now, we are going to modify our recipe-detail
component to read this value from the URL and load the content.
There is a little more to unpack here. We inject the router because we will programmatically redirect away if the user attempts to view a non-existent recipe ID. We use the canLoad
method because loading our recipe view relies on existing recipes. If the recipe can't be found using the API, we redirect to the recipes page programmatically using the router.load
method.
Inside recipe-detail.html
we'll render our recipe:
The API we use returns ingredients on a line-by-line basis, so we've omitted those from this example. Now, you should be able to run your application and click on recipes to view them. A headline, image and instructions should now be visible.
As an additional step, you could add those in yourself.
The router automatically adds a active
class to all route links when they are active. Because our routes all live in my-app
We will edit my-app.css
We don't even have to import it (Aurelia does that automatically for us).
The active
class is now bold when active. We also do some quick styling tweaks to the route links to remove the underline and make them black so we can see the bold stand out more.
You just built a recipe application. It doesn't allow you to create recipes, and it's not very pretty, but it works. To see the application in action, a working demo of the above code can be seen here (or below).
While the docs do a great job explaining the intricacies of the router, sometimes you just need a code snippet and a brief explanation to do something. You will find code snippets for basic things, from creating routes to working with router hooks.
A component that is loaded as part of a route definition. The IRouteableComponent
When working with the router, sometimes you want to access the currently active route. The router provides an array of activeComponents
which can be one or more components currently active. In most instances, this array will only contain one component. However, if you are working with multiple viewports, this array will contain all components from those viewports.
By leveraging the route.match
property, we can get the currently active route. This is where you can access its data, path, name and other route configuration properties.
To get all registered routes, you can use the getRoutes
method from the rootScope
property of the router.
As outlined in the Creating Routes section, routes can be specified using the routes
decorator or the static routes
property.
A parameter is denoted by the prefixed colon :
followed by the parameter's name. In this example, our parameter is called productId
, which is required for the route to load.
You can have more than one parameter (as many as you like):
Routes support a custom data
property allowing you to decorate your routes. Some use cases might include marking a route as requiring a user to be authenticated or an icon.
Some routes might be loaded into specific viewports in applications with multiple viewports. You can use the viewport
property on routes to specify which route.
Inside components displayed by routes, the best place is to load data inside canLoad
or load
hooks. If your view depends on the data being loaded (like a product detail page), use canLoad
otherwise, use load
. The first argument is any parameters passed through the route.
Using the canLoad
lifecycle hook, we can redirect users. In the following example, we redirect a user to a /products
route. You would have this wrapped in a check to determine if the component loads or the user is redirected away.
Please note that in Aurelia2 there are two routers, namely @aurelia/router
and @aurelia/router-lite
(this one). The router-lite one is smaller in size, supports only configured routing, and does not support direct routing, as facilitated by @aurelia/router
. Choose your router depending on your need.
Routing with Aurelia feels like a natural part of the framework. It can easily be implemented into your applications in a way that feels familiar if you have worked with other frameworks and library routers. Here is a basic example of routing in an Aurelia application using router-lite
.
The following getting started guide assumes you have an Aurelia application already created. If not, consult our Quick Start to get Aurelia installed in minutes.
router-lite
To use the router-lite
, we have to register it with Aurelia. We do this at the bootstrapping phase.
To know more about the different configuration options for router-lite, please refer the documentation on that topic.
For this example, we have a root component which is MyApp
. And then we are going to define two routes for the root component, namely home
and about
.
Let us define these components first.
With the routable components in place, let's define the routes. To this end, we need to add the route configurations to our root component MyApp
.
There are couple of stuffs to note here. We start by looking at the configurations defined using the @route
decorator where we list out the routes under the routes
property in the configuration object in the @route
decorator. The most important things in every route configurations are the path
and the component
properties. This instructs the router to use the defined component
in the viewport when it sees the associated path
.
To know more about configuring routes, please refer to the respective documentation.
The viewport is specified in the view (see my-app.html
) by using the <au-viewport>
custom element. For example, the router will use this element to display the Home
component when it sees the /
(the empty path) or the /home
paths.
The nav>a
elements are added to navigate from one view to another.
See this in action:
If you have opened the demo then you can notice that the URL in the address bar or the URLs in the nav>a
elements contains a #
(example: /#home
, /#about
etc.). Depending on your project need and esthetics you may want to get rid of the #
-character. To this end, you need set the useUrlFragmentHash
to false
, which is also the default.
For the documentation of router-lite, many live examples are prepared. It is recommended to use the live examples as you read along the documentation. However, if you are feeling adventurous enough to explore the features by yourself, here is the complete collection of the live examples at your disposal.
The examples are created using StackBlitz. Sometimes, you need to open the examples in a new tab to see changes in the URL, title etc. To this end, copy the URL appearing on the address bar on the right pane and open that in a new tab.
Learn about configuring the Router-Lite.
The router allows you to configure how it interprets and handles routing in your Aurelia applications. The customize
method on the RouterConfiguration
object can be used to configure router settings.
useUrlFragmentHash
If you do not provide any configuration value, the default is pushState routing. If you prefer hash-based routing to be used, you can enable this like so:
By calling the customize
method, you can supply a configuration object containing the property useUrlFragmentHash
and supplying a boolean value. If you supply true
this will enable hash mode. The default is false
.
If you are working with pushState routing, you will need a <base>
element with href
attribute (for more information, refer MDN) in the head of your document. The scaffolded application from the CLI includes this in the index.html
file, but if you're starting from scratch or building within an existing application you need to be aware of this.
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 your preferred server technology's documentation for more information on how to allow 404s to be handled on the client with push state.
basePath
Configuring a base path is useful in many real-life scenarios. One such example is when you are hosting multiple smaller application under a single hosting service. In this case, you probably want the URLs to look like https://example.com/app1/view42
or https://example.com/app2/view21
. In such cases, it is useful to specify a different base#href
value for every app.
Run the following example to understand how the value defined in base#href
is affecting the URLs.
When you open the example in a new browser tab, you can note that the URL in the address bar looks the HOSTING_PREFIX/app/home
or HOSTING_PREFIX/app/about
. This is also true for the href
values in the a
tags. This happens because <base href="/app">
is used in the index.ejs
(producing the index.html). In this case, the router-lite
is picking up the baseURI
information and performing the routing accordingly.
This needs bit more work when you are supporting multi-tenancy for your app. In this case, you might want the URLs look like https://example.com/tenant-foo/app1/view42
or https://example.com/tenant-bar/app2/view21
. You cannot set the document.baseURI
every time you start the app for a different tenant, as that value is static and readonly, read from the base#href
value.
With router-lite
you can support this by setting the basePath
value differently for each tenant, while customizing the router configuration, at bootstrapping phase. Following is an example that implements the aforementioned URL convention. To better understand, open the the example in a new tab and check the URL in address bar when you switch tenants as well as the links in the a
tags.
The actual configuration takes place in the main.ts
while customizing the router configuration in the following lines of code.
There are also the following links, included in the my-app.html
, to simulate tenant switch/selection.
Note the a
tags with external
attribute. Note that when you switch to a tenant, the links in the a
tags also now includes the tenant name; for example when we switch to tenant 'foo' the 'Home' link is changed to /foo/app/home
from /app/home
.
A buildTitle
function can be used to customize the default behavior of building the title. For this example, we assume that we have the configured the routes as follows:
With this route configuration in place, when we navigate to /home
, the default-built title will be Home | Aurelia
. We can use the following buildTitle
function that will cause the title to be Aurelia - Home
when users navigate to /
or /home
route.
Check out the following live example. You might need to open the demo in a new tab to observe the title changes.
Translating the title
When localizing your app, you would also like to translate the title. Note that the router does not facilitate the translation by itself. However, there are enough hooks that can be leveraged to translate the title. To this end, we would use the data
property in the route configuration to store the i18n key.
As data
is an object of type Record<string, unknown>
, you are free to chose the property names inside the data
object. Here we are using the i18n
property to store the i18n key for individual routes.
In the next step we make use of the buildTitle
customization as well as a AppTask
hook to subscribe to the locale change event.
This customization in conjunction with the previously shown routing configuration will cause the title to be Aurelia - Startseite
when user is navigated to /
or /home
route and the current locale is de
. Here we are assuming that the i18n resource for the de
locale contains the following.
The following example demonstrate the title translation.
href
custom attribute using useHref
By default, the router will allow you to use both href
as well as load
for specifying routes. Where this can get you into trouble is external links, mailto:
links and other types of links that do not route. A simple example looks like this:
This seemingly innocent and common scenario by default will trigger the router and will cause an error.
You have two options when it comes to working with external links. You can specify the link as external using the external
attribute.
Or, you can set useHref
to false
(default is true
) and only ever use the load
attribute for routes.
Using the historyStrategy
configuration option it can be instructed, how the router-lite should interact with the browser history object. This configuration option can take the following values: push
, replace
, and none
.
push
This is the default strategy. In this mode, the router-lite will interact with Browser history to push
a new navigation state each time a new navigation is performed. This enables the end users to use the back and forward buttons of the browser to navigate back and forth in an application using the router-lite.
Check out the following example to see this in action.
The main configuration can be found in the main.ts
.
To demonstrate the push
behavior, there is a small piece of code in the my-app.ts
that listens to router events to create informative text (the history
property in the class) from the browser history object that is used in the view to display the information.
As you click the Home
and About
links in the example, you can see that the new states are being pushed to the history, and thereby increasing the length of the history.
replace
This can be used to replace the current state in the history. Check out the following example to see this in action. Note that the following example is identical with the previous example, with the difference of using the replace
-value as the history strategy.
As you interact with this example, you can see that new states are replacing old states, and therefore, unlike the previous example, you don't observe any change in the length of the history.
none
Use this if you don't want the router-lite to interact with the history at all. Check out the following example to see this in action. Note that the following example is identical with the previous example, with the difference of using the none
-value as the history strategy.
As you interact with this example, you can see that there is absolutely no change in the history information, indicating non-interaction with the history object.
You can use the navigation options to override the configured history strategy for individual routing instructions.
Using the activeClass
option you can add a class name to the router configuration. This class name is used by the load
custom attribute when the associated instruction is active. The default value for this option is null
, which also means that the load
custom attribute won't add any class proactively. Note that the router-lite does not define any CSS class out-of-the-box. If you want to use this feature, make sure that you defines the class as well in your stylesheet.
Learn about configuring routes in Router-Lite.
The router takes your routing instructions and matches the URL to one of the configured Routes to determine which components to render. To register routes you can either use the @route
decorator or you can use the static routes
property to register one or more routes in your application. This section describes the route configuration options in details.
The routing configuration syntax for router-lite is similar to that of other routers you might have worked with before. If you have worked with Express.js routing, then the syntax will be very familiar to you.
A route is an object containing a few required properties that tell the router what component to render, what URL it should match on and other route-specific configuration options.
The most usual case of defining a route configuration is by specifying the path
and the component
properties. The idea is to use the path
property to define a pattern, which when seen in the URL path, the view model defined using the component
property is activated by the router-lite. Simply put, a routing configuration is a mapping between one or more path patterns to components. Below is the simple example (from the getting started section) of this.
For the example above, when the router-lite sees either the path /
or /home
, it loads the Home
component and if it sees the /about
path it loads the About
component.
Note that you can map multiple paths to a single component. Although these paths can be thought of as aliases, multiple paths, in combination with path parameters gets interesting. Another way of creating aliases is to use the redirectTo
configuration option.
Note that the example above uses the @route
decorator. In case you cannot use the decorator, you can use the static properties instead. The example shown above can be rewritten as follows.
As the re-written example shows, you can convert the properties in the options object used for the @route
decorator into static
properties in the view model class.
Apart from the static API including the @route
decorator, there is also an instance-level hook named getRouteConfig
that you can use to configure your routes. This is shown in the example below.
See this in action below.
Note that the hook is also supplied with a parent route configuration, and the new route node. These values can be nullable; for example, for root node there is no parent route configuration.
The getRouteConfig
can also be async
. This is shown in the example below.
See this in action below.
path
and parametersThe path defines one or more patterns, which are used by the router-lite to evaluate whether or not an URL matches a route or not. A path can be either a static string (empty string is also allowed, and is considered as the default route) without any additional dynamic parts in it, or it can contain parameters. The paths defined on every routing hierarchy (note that routing configurations can be hierarchical) must be unique.
Required parameters are prefixed with a colon. The following example shows how to use a required parameter in the path
.
When a given URL matches one such route, the parameter value is made available in the canLoad
, and load
routing hooks.
Note that the value of the id
parameter as defined in the route configuration (:id
) is available via the params.id
. Check out the live example to see this in action.
Optional parameters start with a colon and end with a question mark. The following example shows how to use an optional parameter in the path
.
In the example, shown above, the Product
component is loaded when the router-lite sees paths like /product
or /product/some-id
, that is irrespective of a value for the id
parameter. You can see the live example below.
Note that there is an additional link added to the products.html
to fetch a random product.
As the id
parameter is optional, even without a value for the id
parameter, clicking the link loads the Product
component. Depending on whether or not there is a value present for the id
parameter, the Product
component generates a random id and loads that.
The wildcard parameters, start with an asterisk instead of a colon, act as a catch-all, capturing everything provided after it. The following example shows how to use a wildcard parameter in the path
.
In the example, shown above, the Product
component is loaded when the router-lite sees paths like /product/some-id
or /product/some-id/foo/bar
. You can see the live example below.
The example utilizes a wildcard parameter named rest
, and when the value of rest
is 'image'
, an image for the product is shown. To this end, the canLoad
hook of the Product
view-model reads the rest
parameter.
You can configure the title for the routes while you are configuring the routes. The title can be configured in the root level, as well as in the individual route level. This can be seen in the following example using the @route
decorator.
If you prefer using the static routes
property, the title can be set using a static title
property in the class. The following example has exactly the same effect as of the previous example.
With this configuration in place, the default-built title will be Home | Aurelia
when user is navigated to /
or /home
route. That is, the titles of the child routes precedes the base title. You can customize this default behavior by using a custom buildTitle
function when customizing the router configuration.
Note that, instead of a string, a function can also be used for title
to lazily set the title.
By specifying the redirectTo
property on our route, we can create route aliases. These allow us to redirect to other routes. In the following example, we redirect our default route to the home
page and the about-us
to about
page.
You can see this action below.
Note that redirection also works when there are multiple paths/aliases defined for the same component.
You can see this action below.
You can use route parameters for redirectTo
. The following example shows that the parameters from the about-us
path is rearranged to the about
path.
You can see this action below.
We can instruct the router-lite to redirect the users to a different configured path, whenever it sees any unknown/un-configured paths. To this end, we can use the fallback
configuration option. Following example shows how to use this configuration option.
As the example shows, the fallback
is configured as follows.
There is a custom element, named NotFound
, which is meant to be loaded when any unknown/un-configured route is encountered. As you can see in the above example, clicking the "Foo" link that is with un-configured href
, leads to the NotFound
view.
It is recommended that you configure a fallback
at the root to handle the navigation to un-configured routes gracefully.
Another way of defining the fallback
is to use the route-id
. The following example demonstrates this behavior, where the NotFound
view can be reached via multiple aliases, and instead of choosing one of these aliases the route-id
is used to refer the route.
The name of the custom element, meant to be displayed for any un-configured route can also be used to define fallback
. The following example demonstrates this behavior, where not-found
, the name of custom element NotFound
, is used to refer the route.
An important point to note here is that when you are using the custom element name as fallback, you need to ensure that the custom element is registered to the DI container. Note that in the example above, the NotFound
component is registered to the DI container in main.ts
.
A fallback
defined on parent is inherited by the children (to know more about hierarchical routing configuration, refer the documentation). However, every child can override the fallback as needed. The following example demonstrate this. The root has two sibling viewports and two children components can be loaded into each of those by clicking the link. Every child defines their own child routing configuration. The root defines a fallback
and one of the children overrides the fallback
by defining one of its' own. With this configuration in place, when navigation to a un-configured route ('Foo') is attempted for each children, one loads the overridden version whereas the other loads the fallback inherited from the parent (in this case the root).
A function can be used for fallback
. The function takes the following signature.
An example can look like below, where the example redirects the user to NF1
component if an attempt to load a path /foo
is made. Every other attempt to load an unknown path is results loading the NF2
component.
You can also see this in action below.
You can also use non-string fallbacks. For example, you can use a class as the value for fallback
; such as fallback: NotFound
. Or, if you are using a function, you choose to return a class instead of returning a string. These combinations are also supported by router-lite.
Routes can be marked as case-sensitive in the configuration, allowing the navigation to the component only when the case matches exactly the configured path. See the example below where the navigation to the "about" page is only successful when the casing matches.
Thus, only an attempt to the /AbOuT
path loads the About
component; any attempt with a different casing is navigated to the fallback
. See this in action below.
There are few other routing configuration which aren't discussed above. Our assumption is that these options are more involved and might not be used that often. Moreover, to understand the utility of these options fully, knowledge of other parts of the route would be beneficial. Therefore, this section only briefly introduces these options providing links to the sections with detailed examples.
id
— The unique ID for this route. The router-lite implicitly generates a id
for a given route, if an explicit value for this property is missing. Although this is not really an advanced property, due to the fact that a route can be uniquely identified with id
, it can be used in many interesting ways. For example, this can be used to generate the href
s in the view when using the load
custom attribute or using the Router#load
API. Using this property is also very convenient when there are multiple aliases for a single route, and we need a unique way to refer to this route.
transitionPlan
— How to behave when the currently active component is scheduled to be loaded again in the same viewport. For more details, please refer the documentation.
viewport
— The name of the viewport this component should be loaded into. This demands a full fledged documentation of its own. Refer to the viewport documentation for more details.
data
— Any custom data that should be accessible to matched components or hooks. The value of this configuration property must be an object and the object can take any shape (that is there is no pre-defined interface/class for this object). A typical use-case for the data
property is to define the permissions, required by the users, when they attempt to navigate to this route. Refer an example of this.
nav
- Set this flag to false
(default value is true
), to instruct the router not to add the route to the navigation model. This is typically useful to exclude routes from the public navigation menu.
Before finishing the section on the route configuration, we need to discuss one last topic for completeness, and that is how many different ways you can configure the component
. Throughout various examples so far we have seen that components are configured by importing and using those in the routing configuration. However, there are many other ways in which the components can be configured. This section discusses those.
import()
Components can be configured using the import()
or dynamic import. Instead of statically importing the components, those can be imported using import()
-syntax, as the example shows below.
You can see this in action below.
If you are using TypeScript, ensure that the module
property set to esnext
in your tsconfig.json
to support inline import statements.
Components can be configured using only the custom-element name of the component.
However, when configuring the route this way, you need to register the components to the DI.
You can see this configuration in action below.
Components can be configured using a function that returns a class.
You can see this configuration in action below.
Components can be configured using custom element definition.
You can see this configuration in action below.
Components can be configured using custom element instance.
You can see this configuration in action below.
Using router-lite it is also possible to use the routed view model classes directly as routes configuration. While doing so, if no paths have been explicitly configured for the components, the custom element name and aliases can be used as routing instructions. The following example demonstrates that the C1
and C2
classes are used directly as the child routes for the Root
.
The example above implies that router.load('c-1')
, or router.load('c-a')
and router.load('c-2')
, router.load('c-two')
will load the C1
and C2
respectively.
To know more about the router API refer this section.
The examples discussed so far demonstrate the classic use-cases of route configurations where the parents define the child routes. Another aspect of these examples are that all the route configurations are centralized on the parent component. This section provides some examples where that configuration is distributed across different components.
We start by noting that every component can define its own path. This is shown in the following example.
The example shows that both Home
and About
uses the @route
decorator to define their own paths. This reduces the child-route configuration for MyApp
to @route({ routes: [Home, About] })
. The example can be seen in action below.
Note that other properties of route configuration can also be used in this way.
The previous example demonstrates that the Home
and About
components define the title
for themselves. The example can be seen in action below.
While adapting to distributes routing configuration, the parent can still override the configuration for its children. This makes sense, because even if a component defines its own path
, title
etc. the parent may choose to reach (route) the component via a different path or display a different title when the component is loaded. This is shown below, where the MyApp
overrides the routing configurations defined by About
.
This can be seen in action below.
You can also use the @route
decorator and the getRouteConfig
together.
This can be seen in action below.
The advantage of this kind of distributed configuration is that the routing configuration of every component is defined by every component itself, thereby encouraging encapsulation, leaving the routing configuration at the parent level to merely listing out its child components. On the other hand, highly distributed route configuration may prevent easy overview of the configured routes. That's the trade-off. Feel free to mix and match as per your need and aesthetics.
Learn about viewports in Router-Lite and how to configure hierarchical routing.
The <au-viewport>
element, or commonly referred to as viewport (not to confuse with viewport
meta tag), is the "outlet", where the router-lite attaches/loads the components. For a basic example of viewport, please refer the "Getting started"-tutorial. Most of the examples in the preceding sections show the usage of only a single viewport. However, you can use multiple viewports with the sibling viewports and hierarchical routing. These are useful to create different routing layouts. In the subsequent sections, we first discuss about that. Later in this part of the documentation, we focus on the different configuration options available for viewports.
As seen in the "Getting started"-tutorial, a component can define a set of children routes (using either the @route
decorator or the static properties). The child routes can also in turn define children routes of there own. Such route configuration are commonly known as hierarchical route configuration.
To understand it better, let us consider an example. We want to show a list of products, as links, and when a product link is clicked, we display the details of the product.
To this end we want a viewport on the root component which is used to host the list component. The list component itself houses anther viewport, or more accurately a child viewport, which is then used to host the product details.
The route configuration for the root component consists of a single root for the list component and it also houses a single viewport. The Products
component defines its own child route configuration and houses a child viewport. This is shown below.
The IProductService
injected to the Products
is just some service used to populate the product list and has very little relevance to the discussion related to the router. And the extra style is added to display the list and the details side by side. And that's pretty much all you need for a simple hierarchical routing configuration. You can see this in action below.
If you open the example in a new tab, you can see how the URL paths are constructed. For example, when you click a product link, the URL is /42/details
or /products/42/details
. This also means that when you try to navigate to that URL directly, the product details will be loaded from the start. It essentially creates shareable URLs.
Two viewports are called siblings if they are under one parent routing hierarchy. Let us recreate the previous example but using sibling viewports this time.
To this end, we want to viewports, housed side-by-side on the root component. We show the products' list on one viewport and the details of the selected product on the other one.
To this end, let us start with the routing configuration on the root component.
Two routes, one for the list another for the details, are configured on the route. Next we need 2 viewports to on the root component. Let us get that done.
Even though we are not yet done, you can check out our work so far in the live example below.
If you run the example, you can immediately see a "problem" that both the viewports are loading the products' list. Although it is not an error per se, with natural use-case in mind, you probably like to avoid that. Let us fix this "problem" first.
This is happening due to the default value of the default
attribute of the <au-viewport>
that is set to ''
(empty string). This default value enables loading the component associated with the empty path without any additional configuration. This default behavior makes sense as the usage of a single viewport at every routing hierarchy might be prevalent.
However, we need a way to prevent this duplication. To this end, we can bind null
to the default
attribute of a viewport, which instructs the router-lite that this particular viewport should be left out when it is empty (that is no component is targeted to be loaded in this viewport).
You can see in the live example below that this fixes the duplication issue.
We still need a way to load the product details on the second viewport. Note that till now, the two viewports cannot be referentially differentiated from one another; that is if you want to load a component specifically on the first or on the second viewport, there is no way to do this for now. To this end, we need to name the viewports.
Although we name the viewports semantically, it is not necessary, and you are free to choose viewport names, as you like. Lastly, we need to use the load
attribute in the Products
component to construct the URL, or more accurately the routing instruction correctly, such that the details of the product is loaded on the details
viewport.
Using the load
attribute we are instructing the router-lite to load the Product
(using the route-id details
) component, with the id
parameter of the route set to the id
of the current item
in the repeater, in the details
viewport. With the context.bind:null
, we are instructing the router-lite to perform this routing instruction on the root routing context (refer the documentation for the load
attribute for more details). Now, when someone clicks a product link the associated details are loaded in the details
viewport. You can see this in action below.
If you open the example in a new tab, you can see how the URL paths are constructed. For example, when you click a product link, the URL is /details/42@details+products@list
.
As seen in the sibling viewports example, viewports can be named. It is particularly useful when there are multiple sibling viewports present. Note that specifying a value for the name
attribute of viewport is optional, and the default value is simply 'default'
.
In the following example, we have the main
viewport for our main content and then another viewport called sidebar
for our sidebar content.
The names can be used to instruct the router-lite to load a specific component to a specific named viewport. To this end the path syntax is as follows:
The live example below shows this.
Note the load
attributes in the anchor
elements.
In the example, clicking the first anchor loads the products
component in the list
viewport and the details of the product with #{id
} into the details
viewport. The second anchor facilitates loading only the the details of the product with #{id
} into the details
viewport.
For more details about navigating and instructions for router-lite, please refer the documentation.
By default, the routes/components are loaded into the first available viewport, when there is no viewport instruction is present. However, the routes can also be configured, such that a configured route is allowed to be loaded only in a certain viewport. This is useful when you know that a certain component needs to be loaded in a certain viewport, because in that case you can use the simple {path}
instruction instead of the more verbose alternative, the {path}@{viewport-name}
instruction. To this end, use the viewport
option of the route configuration.
In this example, we are specifying that the Products
component needs to be loaded into the list
viewport and the Product
component need to be loaded into the details
viewport. You can also see this in the live example below.
Note the anchor
s in the example that show that the viewport names can now be dropped from the routing instructions.
used-by
The used-by
attribute on the au-viewport
component can be thought of as (almost) the parallel of the viewport
configuration option on route configuration. Using this property on a viewport, you can "reserve" a viewport for particular component(s). In other words, you are instructing the router that no other components apart from those specified can be loaded into a viewport with used-by
set.
In this example, we are instructing the router-lite to reserve the first viewport for ce-two
custom element and the reserve the second viewport for ce-one
custom element. You can see this in the live example below, by clicking the links and observing how the components are loaded into the reserved viewports.
You can reserve a viewport for more than one component. To this end, you can use comma-separated values for the used-by
attribute.
The live example below shows this in action
Although the used-by
attribute feels like a markup alternative of the viewport
configuration option on route configuration, there is a subtle difference. Having the used-by
property on a particular viewport set to X
component, does not prevent a preceding viewport without any value for the used-by
property to load the X
component. This is shown in action in the example below.
Note how clicking the links load the components also in the first viewport without any value for the used-by
.
When no route is loaded into a viewport, a 'default' route is loaded into the viewport. For every viewport, such defaults can be configured using the default
attribute. It is optional to specify a value for this attribute and the empty string (''
) is used as the default value for this property. This explains why the route with empty path (when exists) is loaded into a viewport without the default
attribute set, as seen in the sibling viewports example.
Another path can be used to override the default value of the default
attribute. The following example shows four viewports with varied values for the default
attribute. Whereas the first viewport might be the usual viewport with empty path, the other three specifies different default values. These components are loaded into the viewport, by default when the application is started.
The example below shows this in action.
Note that default
attribute can also be bound to null
, to instruct the router-lite not to load any component into ths viewport when no component is scheduled (either by explicit instruction of implicit availability check) to be loaded into the viewport. This is useful when you have more than one viewports and you want to load the empty path (assuming it is configured) in a particular viewport. In that case, you can bind null
to the default
attribute of the other viewport. To see examples of this, please refer to the sibling viewport section.
If a route cannot be recognized, a fallback route is looked for and loaded (when configured) into the viewport. Such fallback can be configured using the fallback
property of the route configuration. au-viewport
also offers a similar fallback
attribute, using which a fallback component can be configured for a particular viewport. The fallback
attribute is similar to its route configuration counterpart, with only one difference. The fallback
attribute in the au-viewport
, when configured, always takes precedence over the fallback
route configuration option. This is shown in the live example below.
A function for the value of fallback
is also supported. An example looks like as follows, where the example redirects the user to NF1
component if an attempt to load a path /foo
is made. Every other attempt to load an unknown path is results loading the NF2
component.
You can also see this in action below.
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.
href
custom attributeYou 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.
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:
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.
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.
You can target named and/or sibling viewports. To this end, you can use the following syntax.
The following live example, demonstrates that.
The example shows the following variations.
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.
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.
In such cases, the router-lite offers the following syntax to make such navigation possible.
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.
href
custom attributeBy 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
or data-external
attribute on the a
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.
load
custom attributeAlthough 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.
The following sections discuss the various other ways routing instruction can be used with the load
attribute.
params
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.
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.
route
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.
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.
Such ancestor context can then be used to bind the context
property of the load
attribute as follows.
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.
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.
active
statusWhen 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.
This can also be seen in the live example below.
Note that the navigation model also offers a isActive
property.
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.
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.
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.
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.
An array of paths (string) can be used to load components into sibling viewports. The paths can be parameterized or not non-parameterized.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
You can see this in action below.
Using a custom element controller instance is also supported to be used as a value for the context
option. An example looks as follows.
You can see this in action below.
And lastly, you can use the HTML element as context. Following is live example of this.
historyStrategy
Using this navigation option, you can override the configured history strategy. Let us consider the example where three routes c1
, c2
, and c3
are configured with the push
history strategy. Let us also assume that the following navigation instructions have already taken place.
After this, if we issue the following instruction,
then performing a history.back()
should load the c1
route, as the state for c2
is replaced.
transitionPlan
Using this navigation option, you can override the configured transition plan per routing instruction basis. The following example demonstrates that even though the routes are configured with a specific transition plans, using the router API, the transition plans can be overridden.
This can be seen in action below.
For completeness it needs to be briefly discussed that apart from the explicit navigation instruction, there can be need to redirect the user to a different route or handle unknown routes gracefully. Other sections of the router-lite documentation discusses these topics in detail. Hence these topics aren't repeated here. Please refer to the linked documentations for more details.
Fallback using the route configuration
Fallback using the viewport attribute
Learn about the different routing hooks and how to leverage those in terms of dis/allow loading or unloading as well as performing setup and teardown of a view.
Inside your routable components which implement the IRouteViewModel
interface, there are certain methods that are called at different points of the routing lifecycle. These lifecycle hooks allow you to run code inside of your components such as fetch data or change the UI itself.
Router lifecycle hook methods are all completely optional. You only have to implement the methods you require. The router will only call a method if it has been specified inside of your routable component. All lifecycle hook methods also support returning a promise and can be asynchronous.
If you are working with components you are rendering, implementing IRouteViewModel
will ensure that your code editor provides you with intellisense to make working with these lifecycle hooks in the appropriate way a lot easier.
Using the canLoad
and canUnload
hooks you can determine whether to allow or disallow navigation to and from a route respectively. The loading
and unloading
hooks are meant to be used for performing setup and clean up activities respectively for a view. Note that all of these hooks can return a promise, which will be awaited by the router-lite pipeline. These hooks are discussed in details in the following section.
In case you are looking for the global/shared routing hooks, there is a separate documentation section dedicated for that.
canLoad
The canLoad
method is called upon attempting to load the component. It allows you to determine if the component should be loaded or not. If your component relies on some precondition being fulfilled before being allowed to render, this is the method you would use.
The component would be loaded if true
(it has to be boolean
true
) is returned from this method. To disallow loading the component you can return false
. You can also return a navigation instruction to navigate the user to a different view. These are discussed in the following sections.
Returning any value other than boolean true
, from within the canLoad
function will cancel the router navigation.
The following example shows that a parameterized route, such as /c1/:id?
, can only be loaded if the value of id
is an even number. Note that the value of the id
parameter can be grabbed from the the first argument (params
) to the canLoad
method.
You can also see this example in action below.
canLoad
Not only can we allow or disallow the component to be loaded, but we can also redirect. The simplest way is to return a string path from canLoad
. In the following example, we re-write the previous example, but instead of returning false
, we return a path, where the user will be redirected.
You can also see this example in action below.
If you prefer a more structured navigation instructions then you can also do so. Following is the same example using route-id and parameters object.
Note that you can also choose to return a sibling navigation instructions. This can be done by returning an array of navigation instructions.
You can also see the example in action below.
Apart from accessing the route parameter, the query and the fragment associated with the URL can also be accessed inside the canLoad
hook. To this end, you can use the second argument (next
) to this method.
The following example shows that id
query parameter is checked whether that is an even number or not. If that condition does not hold, then user is redirected to a different view with the query and fragment.
You can also see the example in action below.
loading
The loading
method is called when your component is navigated to. If your route has any parameters supplied, they will be provided to the loading
method as an object with one or more parameters as the first argument.
In many ways, the loading
method is the same as canLoad
with the exception that loading
cannot prevent the component from loading. Where canLoad
can be used to redirect users away from the component, the loading
method cannot.
This lifecycle hook can be utilized to perform setup; for example, fetching data from backend API etc.
All of the above code examples for canLoad
can be used with load
and will work the same with the exception of being able to return true
or false
boolean values to prevent the component being loaded.
One of the examples is refactored using loading
hook that is shown below.
Following is an additional example, that shows that you can use the next.title
property to dynamically set the route title from the loading
hook.
canUnload
The canUnload
method is called when a user attempts to leave a routed view. The first argument (next
) of this hook is a RouteNode
which provides information about the next route.
This hook is like the canLoad
method but inverse. You can return a boolean true
from this method, allowing the router-lite to navigate away from the current component. Returning any other value from this method will disallow the router-lite to unload this component.
Returning any value other than boolean true
, from within the canUnload
function will cancel the router navigation.
The following example shows that before navigating away, the user is shown a confirmation prompt. If the user agrees to navigate way, then the navigation is performed. The navigation is cancelled, if the user does not confirm.
You can see this example in action below.
unloading
The unloading
hook is called when the user navigates away from the current component. The first argument (next
) of this hook is a RouteNode
which provides information about the next route.
This hook is like the loading
method but inverse.
The following example shows that a unloading
hook logs the event of unloading the component.
This can also be seen in the live example below.
For completeness it needs to be noted that the canLoad
hook is invoked before loading
and canUnload
hook is invoked before unloading
. In the context of swapping two views/components it is as follows.
canUnload
hook (when present) of the current component is invoked.
canLoad
hook (when present) of the next component (assuming that the canUnload
returned true
) is invoked.
unloading
hook (when present) of the current component is invoked.
loading
hook (when present) of the current component is invoked.
Note that the last 2 steps may run in parallel, if the hooks are asynchronous.
How to implement router "guards" into your applications to protect routes from direct access.
Router hooks are pieces of code that can be invoked at the different stages of routing lifecycle. In that sense, these hooks are similar to the lifecycle hooks of the routed view models. The difference is that these hooks are shared among multiple routed view models. Therefore, even though the hook signatures are similar to that of the lifecycle hooks, these hooks are supplied with an additional argument that is the view model instance.
If you worked with Aurelia 1, you might know these by their previous name: router pipelines.
Shared lifecycle hook logic can be defined by implementing one of the router lifecycle hooks (canLoad
, loading
etc.) on a class with the @lifecycleHooks()
decorator. This hook will be invoked for each component where this class is available as a dependency.
While the router hooks are indeed independent of the components you are routing to, the functions are basically the same as you would use inside of an ordinary component.
This is the contract for ordinary route lifecycle hooks for components:
And the following is the contract for shared lifecycle hooks.
The only difference is the addition of the first viewModel
parameter. This comes in handy when you need the component instance since the this
keyword won't give you access to the component instance like it would in ordinary instance hooks.
Before starting with the involved details of the shared/global lifecycle hooks, let us first create an example lifecycle hook. To this end, we consider the typical use-case of authorization; that is restricting certain routes to users with certain permission claims.
The example we are going to build in this section is just a toy example. For your production code, perform due diligence to evaluate the potential security threats.
For this example, we will create two lifecycle hooks; one for authentication and another is for authorization. However, before directly dive into that, let us briefly visit, how the routes are configured.
Note that the data
property of the route configuration option is used here to define the routes' permission claim. This is used by the auth hooks later to determine whether to allow or disallow the navigation. With that we are now ready to discuss the hooks.
The first hook will check if the current route is protected by a claim and there is a currently logged in user. When there is no logged in user, it performs a redirect to login page. This is shown below.
The second hook will check if the current user has the permission claims to access the route. Where the user does not satisfies the claims requirements the user is redirected to a forbidden page. This is shown below.
Lastly, we need to register these two hooks to the DI container to bring those into action.
Note that the authentication hook is registered before the authorization hook. This ensures that the authentication hook is invoked before than the authorization hook which is also semantically sensible.
To know more about the order of invocation, please refer the respective section.
And that's the crux of it. You can see this example in action below.
Note that even though in the example we limit the the hooks to only canLoad
method, more than one lifecycle methods/hooks can also be leveraged in a shared lifecycle hook (a class decorated by the @lifecycleHooks()
decorator).
The lifecycle hooks can be registered either globally (as it is done in the previous example or as local dependencies.
The globally registered lifecycle hooks are invoked for every components. Thus, it is recommended to use those sparsely. On the other hand, when a hook is registered as a dependency of a particular component, it is invoked only for that one component.
This is shown in the example below, where there are two globally registered hooks, which are invoked for every components.
Note that the globally registered hooks in the example above do nothing significant other than logging the invocations. This is shown below.
The log entries are then enumerated on the view. The following is one such example of log entries.
You may get a different log depending on your test run. However, it can still be clearly observed that both hook1
and hook2
are invoked for every components. Depending on your use-case, that might not be optimal.
To achieve a granular control on the lifecycle hooks, you can register the hooks as the dependencies
for individual routed view models. This ensures that the lifecycle hooks are invoked only for the components where those are registered as dependencies. This shown in the example below where there are three hooks, and one component has two hooks registered as dependencies
and another component has only hook registered.
When ChildOne
or ChildTwo
is loaded or unloaded you can observe that only Hook2
is invoked for ChildTwo
, whereas both Hook1
and Hook2
are invoked for ChildOne
. Below is an example log from one of such test runs.
You can see the example in action below.
You can of course choose to use both kind of registrations. The following example shows that Hook3
is registered globally and therefore is invoked for every components whereas Hook1
is only invoked for ChildOne
and Hook2
is only invoked for ChildTwo
.
When using multiple lifecycle hooks, if any hook returns a non-true
value (either a false
or a navigation instruction) from canLoad
or canUnload
, it preempts invocation of the other hooks in the routing pipeline.
This is shown in the following example. The example shows that there are two hooks, namely hook1
and hook2
. hook1
return false
if the path c1
is navigated with a non-number and non-even number; for example it denies navigation to c1/43
but allows c1/42
.
You can see the example in action below.
If you run the example and try clicking the links, you can observe that once hook1
returns false
, hook2
is not invoked. One such example log is shown below.
The thumb rule is that the hooks are invoked in the order they are registered. That is if some Hook1
is registered before Hook2
in DI then Hook1
will be invoked before the Hook2
. You can see this in the example of globally registered hooks.
That is also true, when registering hooks as one of the dependencies
for a custom element. You can see this in the example of hooks as dependencies.
When using both globally registered hooks as well as local dependencies, the global hooks are invoked before the locally registered hooks. You can see this in action in this example.
Lastly, the shared lifecycle hooks are invoked before the instance lifecycle hooks.
Learn about how to subscribe to and handle router events.
You can use the lifecycle hooks (instance and shared) to intercept different stages of the navigation when you are working with the routed components directly. However, if you want to tap into different navigation phases from a non-routed component, such as standalone service or a simple custom element, then you need to leverage router events. This section discusses that.
The router emits the following events.
au:router:location-change
: Emitted when the browser location is changed via the popstate
and hashchange
events.
au:router:navigation-start
: Emitted by router before executing a routing instruction; or in other words, before performing navigation.
au:router:navigation-end
: Emitted when the navigation is completed successfully.
au:router:navigation-cancel
: Emitted when the navigation is cancelled via a non-true
return value from canLoad
or canUnload
lifecycle hooks.
au:router:navigation-error
: Emitted when the navigation is erred.
The events can be subscribed to using the event aggregator. However, there is another type-safe alternative to that.
To this end, inject the IRouterEvents
and use the IRouterEvents#subscribe
.
Note that the event-data for every event has a different type. When you are using TypeScript, using IRouterEvents
correctly types the event-data to the corresponding event type and naturally provides you with intellisense. This type information won't be available if you subscribe to the events using the event aggregator.
The following example demonstrates the usage of router events, where the root component displays a spinner at the start of navigation, and removes it when the navigation ends.
This is shown in action below.
Create a navigation menu using navigation model in Router-Lite.
The navigation model can be thought of as view-friendly version of the configured routes. It provides similar information as of the configured routes with some additional data to it. This is typically useful when you want to create navigation menu from the already registered/configured routes in the router, without necessarily duplicating the data. The information takes the following shape.
Note that apart from isActive
, all other properties of the route object are same as the corresponding configured route.
This section provides example of how to use navigation model while discussing different aspects of it.
The following example shows how to create a navigation menu using the info from the navigation model.
In this example, we are using a custom element named nav-bar
. In the custom element we inject an instance of IRouteContext
and we grab the navigation model from the routing context.
Then the information from the model is used in the view to create the navigation menu.
It additionally shows that from the NavBar#binding
, INavigationModel#resolve()
is awaited. This is recommended, when dealing with async route configuration. This allows all the promises to be resolved and thereafter building the navigation information correctly.
Note that in the example above we aren't dealing with async routing. Therefore, for that example waiting the INavigationModel#resolve()
can be avoided.
isActive
propertyThe isActive
property is true
when this route is currently active (loaded), and otherwise it is false
. A typical use-case for this property is to apply or remove the "active" style to the links, depending on if the link is active or not. You can see this in the following example where a new .active
class is added and that is bound to the isActive
property.
You can see this in action below.
By default, all configured routes are added to the navigation model. However, there might be routes which is desired to be excluded from the navigation model; for example: a fallback route for un-configured routes. To this end, a route can be configured with nav: false
to instruct the router not to included it in the navigation model.
You see this in action in the example below.
If you are not creating a menu using the navigation model, you can also deactivate the navigation model by setting false
to the useNavigationModel
router option. Doing so, will set the IRouteContext#navigationModel
to null
and skip further processing.
Learn how Router-Lite handles the re-entrance of the same component and how to override the default behavior.
The transition plan in router-lite is meant for deciding how to process a navigation instruction that intends to load the same component that is currently loaded/active, but with different parameters. As the router-lite uses a sensible default based on the user-voice, probably you never need to touch this area. However, it is still good to know how to change those defaults, whenever you are in need to do that (and we all know that such needs will arise from time to time).
Transition plan can be configured using the transitionPlan
property in the routing configuration. The allowed values are replace
, invoke-lifecycles
, none
or a function that returns one of these values.
replace
: This instructs the router to completely remove the current component and create a new one, behaving as if the component is changed. This is the default behavior if the parameters are changed.
invoke-lifecycles
: This instructs the router to call the lifecycle hooks (canUnload
, canLoad
, unloading
and loading
) of the component.
none
: Does nothing. This is the default behavior, when nothing is changed.
The child routes inherits the transitionPlan
from the parent.
When the transitionPlan
property in the routing configuration is not configured, router-lite uses replace
when the parameters are changed and none
otherwise.
It might be normal to think that the default selection of the replace
transition plan when the parameter changes, to be an overkill and the default selection should have been invoke-lifecycles
instead. As a matter of fact that's the default option in Aurelia1 as well as in earlier versions of Aurelia2. However, as understood from the user-voices that replace
would in this case cause less surprises. Hence the default behavior is changed to replace
.
Transition plans defined on the root are inherited by the children. The example below shows that the transitionPlan
on the root is configured to replace
and this transition plan is inherited by the child route configuration. This means that every time the link is clicked, the component is created new and the view reflects that as well.
See this example in action below.
You can use a function to dynamically select transition plan based on route nodes. The following example shows that, where for every components, apart from the root component, invoke-lifecycles
transition plan is selected.
The behavior can be validated by alternatively clicking the links multiple times and observing that the CeOne#id2
increases, whereas CeOne#id1
remains constant. This shows that every attempt to load the CeOne
only invokes the lifecycle hooks without re-instantiating the component every time. You can try out this example below.
This can be interesting when dealing with sibling viewports, as you can select different transition plan for different siblings.
The example above selects invoke-lifecycles
for the CeTwo
and replace
for everything else. When you alternatively click the links multiple times, you can see that CeOne
is re-instantiated every time whereas for CeTwo
only the lifecycles hooks are invoked and the instance is reused. You can see the example in action below.