LogoLogo
HomeDiscourseBlogDiscord
  • Introduction
  • Introduction
    • Quick start
    • Aurelia for new developers
    • Hello world
      • Creating your first app
      • Your first component - part 1: the view model
      • Your first component - part 2: the view
      • Running our app
      • Next steps
  • Templates
    • Template Syntax
      • Attribute binding
      • Event binding
      • Text interpolation
      • Template promises
      • Template references
      • Template variables
      • Globals
    • Custom attributes
    • Value converters (pipes)
    • Binding behaviors
    • Form Inputs
    • CSS classes and styling
    • Conditional Rendering
    • List Rendering
    • Lambda Expressions
    • Local templates (inline templates)
    • SVG
  • Components
    • Component basics
    • Component lifecycles
    • Bindable properties
    • Styling components
    • Slotted content
    • Scope and context
    • CustomElement API
    • Template compilation
      • processContent
      • Extending templating syntax
      • Modifying template parsing with AttributePattern
      • Extending binding language
      • Using the template compiler
      • Attribute mapping
  • Getting to know Aurelia
    • Routing
      • @aurelia/router
        • Getting Started
        • Creating Routes
        • Routing Lifecycle
        • Viewports
        • Navigating
        • Route hooks
        • Router animation
        • Route Events
        • Router Tutorial
        • Router Recipes
      • @aurelia/router-lite
        • Getting started
        • Router configuration
        • Configuring routes
        • Viewports
        • Navigating
        • Lifecycle hooks
        • Router hooks
        • Router events
        • Navigation model
        • Current route
        • Transition plan
    • App configuration and startup
    • Enhance
    • Template controllers
    • Understanding synchronous binding
    • Dynamic composition
    • Portalling elements
    • Observation
      • Observing property changes with @observable
      • Effect observation
      • HTML observation
      • Using observerLocator
    • Watching data
    • Dependency injection (DI)
    • App Tasks
    • Task Queue
    • Event Aggregator
  • Developer Guides
    • Animation
    • Testing
      • Overview
      • Testing attributes
      • Testing components
      • Testing value converters
      • Working with the fluent API
      • Stubs, mocks & spies
    • Logging
    • Building plugins
    • Web Components
    • UI virtualization
    • Errors
      • Kernel Errors
      • Template Compiler Errors
      • Dialog Errors
      • Runtime HTML Errors
    • Bundlers
    • Recipes
      • Apollo GraphQL integration
      • Auth0 integration
      • Containerizing Aurelia apps with Docker
      • Cordova/Phonegap integration
      • CSS-in-JS with Emotion
      • DOM style injection
      • Firebase integration
      • Markdown integration
      • Multi root
      • Progress Web Apps (PWA's)
      • Securing an app
      • SignalR integration
      • Strongly-typed templates
      • TailwindCSS integration
      • WebSockets Integration
      • Web Workers Integration
    • Playground
      • Binding & Templating
      • Custom Attributes
        • Binding to Element Size
      • Integration
        • Microsoft FAST
        • Ionic
    • Migrating to Aurelia 2
      • For plugin authors
      • Side-by-side comparison
    • Cheat Sheet
  • Aurelia Packages
    • Validation
      • Validation Tutorial
      • Plugin Configuration
      • Defining & Customizing Rules
      • Architecture
      • Tagging Rules
      • Model Based Validation
      • Validation Controller
      • Validate Binding Behavior
      • Displaying Errors
      • I18n Internationalization
      • Migration Guide & Breaking Changes
    • i18n Internationalization
    • Fetch Client
      • Overview
      • Setup and Configuration
      • Response types
      • Working with forms
      • Intercepting responses & requests
      • Advanced
    • Event Aggregator
    • State
    • Store
      • Configuration and Setup
      • Middleware
    • Dialog
  • Tutorials
    • Building a ChatGPT inspired app
    • Building a realtime cryptocurrency price tracker
    • Building a todo application
    • Building a weather application
    • Building a widget-based dashboard
    • React inside Aurelia
    • Svelte inside Aurelia
    • Synthetic view
    • Vue inside Aurelia
  • Community Contribution
    • Joining the community
    • Code of conduct
    • Contributor guide
    • Building and testing aurelia
    • Writing documentation
    • Translating documentation
Powered by GitBook
On this page
  • Creating a custom lifecycle hook
  • Anatomy of a lifecycle hook
  • Restricting hooks to specific components
  • Multiple hooks per component/class

Was this helpful?

Export as PDF
  1. Getting to know Aurelia
  2. Routing
  3. @aurelia/router

Route hooks

How to implement router "guards" into your applications to protect routes from direct access.

PreviousNavigatingNextRouter animation

Last updated 2 years ago

Was this helpful?

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 , 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.

Creating a custom lifecycle hook

import Aurelia, { lifecycleHooks } from 'aurelia';
import { Parameters, Navigation, RouterConfiguration, RoutingInstruction } from '@aurelia/router';

@lifecycleHooks()
class NoopAuthHandler {
    canLoad(viewModel, params: Parameters, instruction: RoutingInstruction, navigation: Navigation) { 
        return true; 
    }
}

Aurelia
    .register(RouterConfiguration, NoopAuthHandler)
    .app(component)
    .start();

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.

Anatomy of a lifecycle hook

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:

import { Parameters, IRouteableComponent, Navigation, RoutingInstruction } from '@aurelia/router';

class MyComponent implements IRouteableComponent {
  canLoad(params: Parameters, instruction: RoutingInstruction, navigation: Navigation);
  loading(params: Params, instruction: RoutingInstruction, navigation: Navigation);
  canUnload(instruction: RoutingInstruction, navigation: Navigation);
  unloading(instruction: RoutingInstruction, navigation: Navigation);
}

And this is the contract for shared lifecycle hooks

import { lifecycleHooks } from 'aurelia'; 
import { Parameters, Navigation, RoutingInstruction } from '@aurelia/router';

@lifecycleHooks()
class MySharedHooks {
  canLoad(viewModel, params: Parameters, instruction: RoutingInstruction, navigation: Navigation);
  loading(viewModel, params: Params, instruction: RoutingInstruction, navigation: Navigation);
  canUnload(viewModel, instruction: RoutingInstruction, navigation: Navigation);
  unloading(viewModel, instruction: RoutingInstruction, navigation: Navigation);
  unload(viewModel, instruction: RoutingInstruction, navigation: Navigation);
}

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.

Restricting hooks to specific components

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.

import { IRouteableComponent } from "@aurelia/router";
import { AuthHook } from './route-hook';

export class SettingsPage implements IRouteableComponent {
    static dependencies = [ AuthHook ];
}

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.

Multiple hooks per component/class

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.

import { lifecycleHooks } from 'aurelia';

@lifecycleHooks()
class Log1 {
    async loading() {
        console.log('1.start');
        await Promise.resolve();
        console.log('1.end');
    }
}

@lifecycleHooks()
class Log2 {
    async loading() {
        console.log('2.start');
        await Promise.resolve();
        console.log('2.end');
    }
}

export class MyComponent {
    static dependencies = [Log1, Log2];

    async loading() {
        console.log('3.start');
        await Promise.resolve();
        console.log('3.end');
    }
}

// Will log, in order:
// 1.start
// 2.start
// 3.start
// 1.end
// 2.end
// 3.end

It is also permitted to define more than one hook per shared hook class:

@lifecycleHooks()
export class LifecycleLogger {
    canLoad(viewModel, params, instruction, navigation) {
        console.log(`invoking canLoad on ${instruction.component.name}`);
        return true;
    }

    loading(viewModel, params, instruction, navigation) {
        console.log(`invoking load on ${instruction.component.name}`);
    }
}
Lifecycle Hooks