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.
Hook summary
Hook
When it runs
Common uses
Return type
canLoad
Before the component is activated
Gate routes, redirect, hydrate parameters
boolean/NavigationInstruction/Promise
loading
After navigation is approved but before render
Fetch data, set up state, start animations
void/Promise<void>
loaded
After the component is activated and swapped into view
Fire analytics, scroll, kick off post-render effects
void/Promise<void>
canUnload
Before the current component is deactivated
Prevent leaving, confirm unsaved changes
boolean/Promise<boolean>
unloading
Before the component is removed
Persist drafts, dispose resources, log analytics
void/Promise<void>
This page covers component-scoped hooks implemented on IRouteViewModel. For cross-cutting logic that applies to many components, see Router hooks.
Using the canLoad and canUnload hooks you can determine whether to allow or disallow navigation to and from a route respectively. The loading, loaded, and unloading hooks are meant to be used for performing setup, post-activation work, 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 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.
To disallow loading the component you can return a booleanfalse. You can also return a navigation instruction to navigate the user to a different view. These are discussed in the following sections.
Allow or disallowed loading components
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.
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.
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.
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.
If a child component needs to inspect parameters defined by its parent route, inject IRouteContext and use its parent property inside the loading hook.
Nested routes frequently need identifiers that were captured higher in the URL such as /company/:companyId/project/:projectId/user/:userId. Instead of manually walking the parent chain, resolve IRouteContext and call the getRouteParameters() helper to get a merged, read-only view of every matched segment.
getRouteParameters() automatically prefers the closest route's keys when there are duplicates. Pass { mergeStrategy: 'parent-first' } to let ancestors win, { mergeStrategy: 'append' } to receive arrays ordered from parent to child, or { mergeStrategy: 'by-route' } to map each value to the route id that produced it. Combine any strategy with includeQueryParams: true to pull query-string data into the result—see the IRouteContext API reference for details.
The loaded hook runs after the router has activated your component, swapped it into the viewport, and scheduled any nested children. At this stage the DOM for the route is in place, making it a safe spot to start animations, set focus, send analytics, or kick off work that depends on rendered layout.
Just like loading, returning a promise keeps the navigation paused until the hook resolves, ensuring that post-activation work completes before the router signals navigation end.
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 false from this method, to disallow the router to navigate away from the current component.
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.
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.
To detect the direction of navigation, you can use the isBack property from the options: INavigationOptions parameter in the lifecycle hooks. The isBack property will be true if the navigation is a backward navigation (e.g., user clicked the browser's back button) and false for forward navigation.
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.
Order of invocations of component lifecycle hooks
The component lifecycle hooks are invoked bottom-up. As an example, let us assume that we have the following constellation of components.
In this case, the component lifecycle hooks are invoked in the following order.
component-two attached.
component-one attached.
app-root attached.
This is also the same for the router, except for the "application root" component. Tweaking the example above slightly, let us assume that we have the following constellation of components.
In this case, the component lifecycle hooks are invoked in the following order.
app-root attached.
component-two attached.
component-one attached.
routed-view attached.
Note that the application root is attached before any other components are attached. This happens because the router starts loading the first route only after the app-root, and thereby the viewport(s) it is hosting, are fully activated/attached. In order to load a route, the router needs registered viewports. The registration process of a viewport only happens during the attaching phase of a viewport. More details on this topic, can be found in this GitHub issue.
Inspecting current route and query inside lifecycle hooks
Within lifecycle hooks like canLoad, loading, etc., you can also inspect the RouteNode:
If you prefer, you can also inject ICurrentRoute for a global view of the route, query, and title. Combine these approaches as you see fit for your canLoad, loading, etc.
import { Params } from '@aurelia/router';
import { customElement } from '@aurelia/runtime-html';
@customElement({
name: 'c-one',
template: `c1 \${id}`,
})
export class ChildOne {
private id: number;
public canLoad(params: Params): boolean {
const id = Number(params.id);
if (!Number.isInteger(id) || id % 2 != 0) return false;
this.id = id;
return true;
}
}
import { NavigationInstruction, Params } from '@aurelia/router';
import { customElement } from '@aurelia/runtime-html';
@customElement({
name: 'c-one',
template: `c1 \${id}`,
})
export class ChildOne {
private id: number;
public canLoad(params: Params): boolean | NavigationInstruction {
const id = Number(params.id);
// If the id is not an even number then redirect to c2
if (!Number.isInteger(id) || id % 2 != 0) return `c2/${params.id}`;
this.id = id;
return true;
}
}
import { NavigationInstruction, Params } from '@aurelia/router';
import { customElement } from '@aurelia/runtime-html';
@customElement({
name: 'c-one',
template: `c1 \${id}`,
})
export class ChildOne {
private id: number;
public canLoad(params: Params): boolean | NavigationInstruction {
const id = Number(params.id);
if (!Number.isInteger(id) || id % 2 != 0)
return { component: 'r2', params: { id: params.id } };
this.id = id;
return true;
}
}
import { NavigationInstruction, Params } from '@aurelia/router';
import { customElement } from '@aurelia/runtime-html';
import { Workaround } from './workaround';
@customElement({
name: 'c-one',
template: `c1 \${id}`,
})
export class ChildOne {
private id: number;
public canLoad(params: Params): boolean | NavigationInstruction {
const id = Number(params.id);
if (!Number.isInteger(id) || id % 2 != 0)
return [
{ component: Workaround },
{ component: 'r2', params: { id: params.id } },
];
this.id = id;
return true;
}
}
import { resolve } from '@aurelia/kernel';
import { IRouteViewModel, Params, RouteNode } from '@aurelia/router';
import { IPlatform } from '@aurelia/runtime-html';
export class ChildOne implements IRouteViewModel {
private readonly platform: IPlatform = resolve(IPlatform);
public canUnload(next: RouteNode, current: RouteNode): boolean {
const from = current.computeAbsolutePath();
const to = next.computeAbsolutePath();
return this.platform.window.confirm(
`Do you want to navigate from '${from}' to '${to}'?`
);
}
}
public unloading(next: RouteNode): void {
this.logger.log(
`unloading for the next route: ${next.computeAbsolutePath()}`
);
}
import { IRouteViewModel, INavigationOptions, Params, RouteNode } from '@aurelia/router';
import { customElement } from '@aurelia/runtime-html';
@customElement({
name: 'my-component',
template: `<div class="page">My Component Content</div>`,
})
export class MyComponent implements IRouteViewModel {
public loading(
params: Params,
next: RouteNode,
current: RouteNode | null,
options: INavigationOptions
): void {
if (options.isBack) {
console.log('User navigated back to this component');
// Apply different logic for back navigation
} else {
console.log('User navigated forward to this component');
// Apply logic for forward navigation
}
}
public unloading(
next: RouteNode | null,
current: RouteNode,
options: INavigationOptions
): void {
if (options.isBack) {
console.log('User is navigating back from this component');
} else {
console.log('User is navigating forward from this component');
}
}
}