Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 147 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

The Aurelia 2 Docs

Loading...

Getting Started

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Templates

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Components

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Getting to know Aurelia

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Routing

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Router-lite

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Developer Guides

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Quick start

Before you can build an application with Aurelia, you need to meet a couple of prerequisites.

Introduction

Aurelia was designed to make building web applications and rich UI's easy. Living by the mantra of; Simple, Powerful and Unobtrusive, you have everything you need right out of the box to start building feature-rich web applications.

This section aims to give you a brief technical overview of how Aurelia works, but it is not intended to be a tutorial or reference. If you are looking for a nice gentle introduction to Aurelia that will only take a few minutes of your time the Hello World tutorial is a great place to start.

Core concepts

The core concepts of Aurelia are simple; HTML, CSS and Javascript. While Aurelia does introduce its own templating language you will need to become familiar with, it is enhanced HTML, so the syntax and concepts will feel familiar to you already.

View models and views

Underpinning Aurelia is the view-model and view convention. Out-of-the-box, Aurelia assumes that your components are made up of both a view model (.js or .ts file) as well as an accompanying view with a .html file extension.

If you have worked with .NET or other frameworks that use similar concepts, the view-model and view paradigm will be familiar with you. The view-model contains the business logic and data, the view is responsible for displaying it.

Like all concepts in Aurelia, these are default conventions and can be changed as needed.

Dependency injection

Much like other fully-featured Javascript frameworks and once again, .NET, Aurelia features a robust dependency injection system that allows you to manage your in-app dependencies. You will learn about the benefits of dependency injection in other parts of the documentation, but it plays a fundamental part in Aurelia.

Install node.js

To create new Aurelia applications using the Aurelia CLI via Makes, you will need to ensure you have Node.js installed. The latest version of Node is always recommended and preferable. While Node can be installed through different methods, the easiest is to obtain a distributable from the .

Create an app

Aurelia does not require you to install any global Node packages, instead of using the scaffolding tool, new Aurelia applications can be generated by running the following command.

You will then be presented with the Aurelia CLI screen which will first ask for a project name, then guide you through some options. The fastest way to get started is by choosing either the ESNext or TypeScript default options (1 and 2).

We highly recommend choosing TypeScript for any new Aurelia applications. With TypeScript you get the benefits of intellisense and type safety.

Run the app

Once the CLI process is finished and you have installed the dependencies for your new app, run npm start in the project directory, a browser window will open up with the new Aurelia application you just generated running.

Ready to dive deeper?

Take a look at our beginner-friendly obligatory hello world introduction to Aurelia . And if you want to dive even deeper (perhaps you've dabbled in Aurelia before) we have a fantastic selection of tutorials .

Introduction

Get acquainted with Aurelia, the documentation, and how to get started.

What is Aurelia?

Aurelia is an open-source, JavaScript, front-end platform designed to enable you to easily build even the most demanding web, mobile, or desktop applications. Aurelia stands out for its commitment to open web standards and no-nonsense, get-out-of-your-way conventions that enable vanilla JavaScript development. Of course, Aurelia is also packed full of features, performs at the highest standards, is extensible to its core, and supports a fully testable component-oriented design. But you knew all that already, didn't you? 😎

To get started, jump to our Quick Start Guide, or read on to get more of an overview.

Why choose Aurelia?

There are many frameworks to choose from today. We believe that Aurelia provides a fresh and exciting approach to front-end development with power and flexibility unmatched by other options. That said, we recognize that each team and each project has different needs. You might find Aurelia to be the right choice for you if...

  • You want an all-in-one solution - Aurelia provides core capabilities like dependency injection, templating, routing and pub/sub, so you don't have to piece together a bunch of libraries to build an application. On top of this rich core, Aurelia also provides many additional plugins for internationalization, validation, modal dialogs, UI virtualization and much more. You also don't have to cobble together a bunch of different tools. Aurelia provides a CLI for generating and building projects, a browser plugin for debugging and a VS Code plugin as well. Yet, you're not forced to use any of these as Aurelia is structured to enable you to swap out any detail, even down to the templating/binding engine, to guarantee maximum flexibility.

  • You need blazing rendering speed and great memory efficiency - In 3rd-party benchmarks, Aurelia renders faster than any other framework today. Because of its batched rendering and observable object pooling, Aurelia utilizes less memory and causes less GC churn than other frameworks.

  • You require the safety of uni-directional data-flow, but need the productivity of data-binding - Aurelia features an observable-based binding system that uses uni-directional data-flow by default, pushing data from your model into your view via a highly efficient, DOM-batching mechanism. Two-way binding can also be leveraged for HTML form controls, allowing for increased developer productivity, without sacrificing the safety of uni-directional flow or of component encapsulation.

  • You desire API stability amidst a turbulent JavaScript landscape - Aurelia follows Semver and works hard not to make breaking changes to its APIs. We're proud to say that we've continued to innovate and advance the platform while no breaking changes to core framework APIs since our 1.0 release on July 27, 2016.

  • You value high standards compliance - Focused on ES2015+ and W3C Web Components while avoiding unnecessary abstractions; Aurelia provides the cleanest and most standards-compliant component model you'll find anywhere.

  • You think a framework should "get out of your way" - Aurelia is the only framework that lets you build components with plain, vanilla JavaScript/TypeScript. The framework stays out of your way, so your code remains clean and easy to evolve over time.

  • You like programming models that are easy to learn and remember - Because of its simple, consistent design, developers are able to learn a very small set of Aurelia patterns and APIs while unlocking limitless possibilities. Simple conventions help developers follow solid patterns and reduce the amount of code they have to write and maintain. This all results in less fiddling with the framework and more focus on the application.

  • You prefer a platform that integrates well with other frameworks and libraries - Because of the extensible design of Aurelia and its strict adherence to web standards, it's easy to integrate Aurelia with any 3rd party library or framework, including jQuery, React, Polymer, Bootstrap, MaterializeCSS and many more.

  • You love or want to be a part of open source - Aurelia is open-sourced under the MIT license and doesn't add or remove special clauses or conditions to the license. We're proud of the work our community has done together, and we'd love you to join in and help us make Aurelia better for everyone.

  • You thrive on being part of a welcoming community - With an active core team and welcoming Discord server, Aurelia has an amazing community. Our core team and community love to welcome new developers, and we all work hard to help each other succeed.

Using the Docs

Welcome to the Aurelia docs! This is where you will find guides, tutorials, API documentation, examples, resources, and more. We recommend that you begin your journey in the "Getting Started" section with the Quick Start Guide where you will learn to build your first application and be introduced to the basics of Aurelia. After you complete the Quick Start, you should continue reading through the "Getting Started" section where you will learn the bulk of what is needed to get up and running with your own apps. Next, the "App Basics" section is filled with a variety of topics to help you begin to use Aurelia on a regular basis.

Once you feel more comfortable using Aurelia, the "Advanced Scenarios" section covers performance optimization, large-scale projects, UI architecture, and more. There is also a detailed "API" reference for all of Aurelia's classes, methods, and properties. However, it is likely that you will find that you will not need the raw API documentation as much as with other frameworks or libraries since Aurelia focuses on conventions and vanilla JavaScript. Instead, you might find the "Examples" section more helpful since it includes code samples for common scenarios that can be copied and pasted into your own app. Also, the "Resources" section includes a basic FAQ, information about browser support, versioning, comparisons to other frameworks, and various other information you might find useful.

If you happen to find anything in the documentation that is incorrect or missing, every page has an "Edit on GitHub" link that you can use to easily report or correct the issue.

How to Be Part of the Community

As you become more and more familiar with Aurelia, perhaps you will even come up with something that you would like to share with the community. 🎉 The Aurelia team welcomes any contributions that you feel would be beneficial to others in the community. If that is the case, check out the "Community Contribution" section. We also welcome any feedback or suggestions that will help improve Aurelia and enrich the community. Check out the "Contributor Guide" for details about the contributing process and how to contact us.

Where To Begin

You're in the right place. If you are just getting started with Aurelia, we highly recommend that you read through our Quick Start Guide. It will show you how to create a project and build your first components using Aurelia's powerful templating system.

Hello world
Node.js website
Makes
here
here
npx makes aurelia

Aurelia for new developers

New to Javascript, Node.js and front-end development in general? Don't worry, we got you.

Welcome to the magical world of Javascript development. This guide is for any newcomer to front-end development who isn't that experienced with modern tooling or Javascript frameworks.

Getting started

For the purposes of this tutorial and as a general rule for any modern framework like Aurelia, you will be using a terminal of some sort. On Windows, this can be the Command Prompt or Powershell. On macOS, it'll be Terminal (or any other Terminal alternative), the same thing with Linux.

To work with Aurelia, you will need to install Node.js. If you are new to Node.js, it is used by almost every tool in the front-end ecosystem now, from Webpack to other niche bundlers and tools. It underpins the front-end ecosystem.

Next steps

Understand what areas of the framework to learn next, and how to proceed from here on out.

In our hello world example we touched upon some very basic and light Aurelia concepts. The most important of those is the view/view-model relationship. However, please note that you can write Aurelia applications in a wide variety of different ways, including custom elements without view models.

It is highly recommended that you have a read through additional sections in the "Getting Started" section to become familiar with the templating syntax, building components with bindable properties, looping through collections and more.

Once you are familiar with Aurelia's templating syntax and conventions, you will discover that you end up applying these templating basics to every app that you build.

AUR0005

Invalid resolver strategy specified: yyyy

Error message

Invalid resolver strategy specified: yyyy

Parameters

strategy(string)

Error explanation

This means the internal state of the Internal Resolver has been modified, into an invalid value**.**

Possible solutions

Check your code where there's an invalid assignment to a resolver strategy, that may look like resolver.strategy = ...

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

Recipes

Learn how to achieve certain tasks in Aurelia applications. From working with third-party libraries to working with different types of data.

AUR0008

Unable to resolve key: yyyy

Error message

Unable to resolve key: yyyy

Parameters

key(string)

Error explanation

This means a container has failed to resolve a key in the call container.get(key).

Possible solutions

This requires specific debugging as it shouldn't happen, with all the default strategies to resolve for various kinds of keys.

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

AUR0001

Attempted to jitRegister an intrinsic type: yyyy. Did you forget to add @inject(Key)

Error message

Attempted to jitRegister an intrinsic type: yyyy. Did you forget to add @inject(Key)

Parameters

Interface name

Error explanation

A DI container is trying to resolve an instance of an interface, but there is no registration for it. This means the instance you are trying to load has not been registered with Dependency Injection.

Possible solutions

Ensure that you are registering your interface with Aurelia. This can be done inside of the register method on the Aurelia instance or through the DI methods themselves.

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

AUR0013

Cannot call resolve yyyy before calling prepare or after calling dispose.

Error message

Cannot call resolve yyyy before calling prepare or after calling dispose.

Parameters

name(string)

Error explanation

An InstanceProvider.resolve() call happens without having an any instance provided.

Possible solutions

Call InstanceProvider.prepare(instance) before resolving, or instantiate the InstanceProvider with an instance in the 2nd parameter.

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

AUR0002

yyyy not registered, did you forget to add @singleton()?

Error message

yyyy not registered, did you forget to add @singleton()?

Parameters

Name of the key being resolved

Error explanation

A DI container is trying to resolve a key, but there's not a known strategy for it.

Possible solutions

Try adding a strategy for your resolved key. You can do this using @singleton or other forms of DI resolution

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

AUR0010

Attempted to jitRegister something that is not a constructor: 'yyyy'. Did you forget to register this resource?

Error message

Attempted to jitRegister something that is not a constructor: 'yyyy'. Did you forget to register this resource?

Parameters

key(any)

Error explanation

This means a container.get(key) call happens with key being built in type functions such as String/Number/Array etc.

Possible solutions

This could happen from TS generated code where it fails to generate proper metadata, or forgotten registration, consider checking the output of TS when emitDecoratorMetadata is on, or remember to register a resolution for those built-in types.

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

Hello world

Learn the basics of Aurelia by building an interactive Hello, World! application

This guide will take you through creating a hello world app using Aurelia and briefly explain its main concepts. We will be building a simple hello, world example with a text field you can enter a name into and watch the view update. We assume you are familiar with JavaScript, HTML, and CSS.

Here's what you'll learn...

  • How to set up a new Aurelia project.

  • Creating components from view-models and views.

  • The basics of templating and binding.

  • Where to go from here.

When you complete this guide, you'll be ready to take your first steps building your own Aurelia app and, you'll have the resources you need to continue into more advanced topics. So, what are you waiting for? Let's get started!

Creating your first app

Learn to use Aurelia's project scaffold tooling to create your first project setup.

There are various ways that you can set up an Aurelia project, including everything from adding a simple script tag to your HTML page to creating a custom Webpack configuration. One of the easiest and most powerful ways to get started is by using the makes tool.

Before you run makes, you will need a recent version of Node.js installed on your machine. If you do not have Node.js, you can get it here. Please ensure that Node.js is up-to-date if you already have it installed.

Next, using npx, a tool distributed with Node.js, we'll create a new Aurelia app. Open a command prompt and run the following command:

npx makes aurelia

makes will then start the Aurelia project wizard, asking you a few questions to help you get things set up properly. When prompted, give your project the name "hello-world" and then select a default setup, either ESNext (Javascript) or TypeScript, depending on your preference. Finally, choose "yes" to install the project dependencies.

You have now created a hello world app without writing a single line code, well done. However, we'll be updating our app to allow for text input so we can make it say, "Hello" to whoever we want in the next section.

You now have an Aurelia setup ready for you to run, debug, or deploy. To ensure that everything is working properly, cd into your project folder and run npm start. Your project will then build and a web browser will open, displaying the message "Hello World".

Congratulations! 🎊 You just ran your first Aurelia app. Now, let's get building.

The easiest way to install Node.js is from the official website here. Download the installer for your operating system and then follow the prompts.

Download a code editor

To write code, you need an editor that will help you. The most popular choice for Javascript development is Visual Studio Code. It is a completely free and open-source code editor made by Microsoft, which has great support for Aurelia applications and Node.js.

Create a new Aurelia project

We will be following the instructions in the Quick install guide to bootstrap a new Aurelia application. After installing Node.js, that's it. You don't need to install anything else to create a new Aurelia application, here's how we do it.

Open up a Terminal/Command Prompt window and run the following:

You are going to be presented with a few options when you run this command. Don't worry, we'll go through each screen step by step.

Step 1. Name your project

You will be asked to enter a name for your project, this can be anything you want. If you can't think of a name just enter my-app and then hit enter.

Step 2. Choose your options

In step 2 you will be presented with three options.

  • Option one: "Default ESNext Aurelia 2 App" this is a basic Aurelia 2 Javascript application using Babel for transpiling and Webpack for the bundler.

  • Option two: "Default Typescript Aurelia 2 App" this is a basic Aurelia 2 TypeScript application with Webpack for the bundler.

  • Option three: "Custom Aurelia 2 App" no defaults, you choose everything.

In this guide, we are going to go with the most straightforward option, option #1.

Step 3. Install the dependencies

You are going to be asked if you want to install the Npm dependencies and the answer is yes. For this guide we are using Npm, so select option #2.

Depending on your internet connection speed, this can take a while.

Step 4. Run the sample app

After the installation is finished you should see a little block of text with the heading, "Get Started" follow the instructions. Firstly, cd my-app to go into the directory where we installed our app. Then run npm start to run our example app.

Your web browser should open automatically and point to http://localhost:9000

Any changes you make to the files in the src directory of your app will cause the dev server to refresh the page with your new changes. Edit my-app.html and save it to see the browser update. Cool!

Building your app

In the last section we created a new application and ran the development server, but in the "real world" you will build and deploy your site for production.

Run the Npm build command by running the following in your Terminal or Command Prompt window:

This will build your application for production and create a new folder called dist.

npx makes aurelia
npm run build

Advanced Attribute Binding)

How it works

Aurelia, by default, will automatically bind properties to attributes using the native binding syntax. This is accomplished by using a mapping function that converts an aurelia property into a native html attribute. See the attribute mapper for an example of how this is done.

Attributes are turned into camel-case equivalent properties by default if there's no mapping specified, so for example, some-fake-attribute.bind="prop" will set someFakeAttribute on the element properties to the value of prop.

In some cases though, not all properties will be mapped automatically to attributes. In these cases, you have several options.

Attribute tag

One method is to replace .bind with .attr. This will ensure that the property is correctly mapped to the attribute.

Attribute binding behavior

Another option is to apply the attribute (`.attr). This also gives you the ability to specify binding type.

Extend the attribute mapper

TODO: This section is in progress

Using observerLocator

The Observer Locator API allows you to watch properties in your components for changes without the need for using the @observable decorator. In most cases, manual observation will not be required using this API, but it is there if you want it.

By default, an observer locator is used to create observers and subscribe to them for change notification.

A basic observer has the following interface:

interface IObserver {
  subscribe(subscriber)
  unsubscribe(subscriber)
}

The subscribe method of an observer can be used to subscribe to the changes that it observes. This method takes a subscriber as its argument.

A basic subscriber has the following interface:

interface ISubscriber {
  handleChange(newValue, oldValue)
}

An observer of an object property can be retrieved using an observer locator.

An example of this is:

And to subscribe to changes emitted by this observer:

Running our app

If you followed along with our tutorial, you would now have a functional hello world application. The application will function like the one below. Typing into the text input field should dynamically display the value you entered.

Your first component - part 1: the view model

A view-model is where your business logic will live for your components. Follow along as you create your first view-model.

Aurelia as a default convention works on the premise of a view and view-model, both of which are tied together. The view-model is where your business logic lives and, if you have ever worked with .NET before (or other frameworks), you might refer to this as the controller.

Navigate to the src directory where your application code lives and open up the my-app(.ts/.js file. This is the main entry point for the application and this file is where business logic would live. As you can see, there is not a lot going on at the moment.

The class property message contains a string and within our view, we are displaying it using interpolation.

We are now going to create a new component which will be a smarter hello component. Inside of src create a new file called hello-name

Observing property changes with @observable

Learn how to work with Aurelia's observable decorator to create reactive properties inside your component view models that have change callbacks.

Unlike the , the @observable decorator allows you to decorate properties in a component and optionally call a change callback when the value changes. It works quite similarly to the @bindable property.

By convention, the change handler is a method whose name is composed of the property_name and the literal value 'Changed'. For example, if you decorate the property color with @observable, you have to define a method named colorChanged() to be the change handler.

This is what a basic observable would look like using conventions:

When the color

HTML observation

Quick introduction

HTML elements are special objects that often require different observation strategies, and most of the time, listening to some specific event is the preferred way. For this reason, Aurelia encourages using events to observe HTML elements.

As an example, the value property of an <input /> element should be observed by listening to the <input /> change events such as input or

What is Dependency Injection?

Learn about why Dependency Injection (DI) is so important and what role it plays in Aurelia.

What is DI, and why would I use it?

As a system increases in complexity, it becomes more and more important to break complex code down into groups of smaller, collaborating functions or objects. However, once we’ve broken down a problem/solution into smaller pieces, we have introduced a new problem: how do we put the pieces together?

One approach is to have the controlling function or object directly instantiate all its dependencies. This is tedious but also introduces the bigger problem of tight coupling and muddies the controller's primary responsibility by forcing upon it a secondary concern of locating and creating all dependencies. Inversion of Control (IoC) can be employed to address these issues.

Simply put, the responsibility for locating and/or instantiating collaborators is removed from the controlling function/object and delegated to a 3rd party (the control is inverted).

Viewports

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.

Named viewports

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

SVG

A developer guide for enabling SVG binding in the Aurelia.

Learn about enabling SVG binding in Aurelia template.

Adding SVG registration

By default, Aurelia won't work with SVG elements, since SVG elements and their attributes require different parsing rules. To teach Aurelia how to handle SVG element bindings, add the SVGAnalyzer like the following example:

After adding this registration, bindings with attributes will work as expected and the syntax is the same with the other bindings. Readmore on the basic binding syntax of Aurelia .

Observation

Observe changes in your applications.

Aurelia provides a multitude of different wants to observe properties in your components and call a callback function when they change.

The following sections in the observation documentation will help you decide which observation strategy is appropriate for your applications, from the most commonly used to more advanced observation strategies.

The @observable approach

The easiest way to watch for changes to specific view model properties is using the @observable decorator which provides an easy way to watch for changes to properties and react accordingly.

AUR0004

Resolver for yyyy returned a null factory

Error message

Resolver for yyyy returned a null factory

AUR0003

Cyclic dependency found: name

Error message

Cyclic dependency found: name

Parameters

AUR0012

Attempted to jitRegister an interface: yyyy

Error message

Attempted to jitRegister an interface: yyyy

AUR0014

key/value cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI?

Error message

key/value cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI?

AUR0009

Attempted to jitRegister something that is not a constructor: 'yyyy'. Did you forget to register this resource?

Error message

Attempted to jitRegister something that is not a constructor: 'yyyy'. Did you forget to register this resource?

Typically, this means that all dependencies become parameters of the function or object constructor, making every function/object implemented this way not only decoupled but open for extension by providing different implementations of the dependencies. The process of providing these dependencies to the controller is known as Dependency Injection (DI).

Once again, we’re back at our original problem: how do we put all these pieces together? With the control merely inverted and open for injection, we are now stuck having to manually instantiate or locate all dependencies and supply them before calling the function or creating the object…and we must do this at every function call site or every place that the object is instanced. It seems as if this may be a bigger maintenance problem than we started with!

Fortunately, there is a battle-tested solution to this problem. We can use a Dependency Injection Container. With a DI container, a class can declare its dependencies and allow the container to locate them and provide them to the class. Because the container can locate and provide dependencies, it can also manage the lifetime of objects, enabling singleton, transient and object pooling patterns without consumers needing to be aware of this complexity.

Parameters

String version of the key being resolved

Error explanation

No factory was found for transient registration.

Possible solutions

This means the transient registration you gave to a container wasn't with a proper factory registered along with it, consider using container.registerFactory(IMyInterface, someFactoryObject) to fix this issue.

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

Parameters

name(string)

Error explanation

container.get(key) was called with key being an interface with no prior registration

Possible solutions

Register the interface with the container before calling container.get().

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

Error explanation

A key was null/undefined in a container.get/.getAll call

Possible solutions

Make sure the key is not null/undefined. This sometimes can happen with bundler that leaves circular dependency handling to applications, e.x: Webpack.

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

Parameters

key(any)

Error explanation

This means a container.get(key) call happens without any prior knowledge for the container to resolve the key given. And the container is unable to instantiate this key as it's not a class (or a normal function).

Possible solutions

Consider registering the key with the container, or parent or root containers before making the call.

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

AUR0007

Resource key "yyyy" already registered

Error message

Invalid resolver strategy specified: yyyy

Parameters

resource key

Error explanation

This means there is a resource with that name already registered with a container

Possible solutions

Consider using a different name for the resource (element/attribute/value converter/binding behavior etc...).

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

AUR0015

yyyy is a native function and, therefore cannot be safely constructed by DI. If this is intentional, please use a callback or cachedCallback resolver.

Error message

yyyy is a native function and, therefore cannot be safely constructed by DI. If this is intentional, please use a callback or cachedCallback resolver.

Parameters

name(string)

Error explanation

A container.invoke(key) or container.getFactory(key) call happens with the key being one of the built-in types like String/Number/Array

Possible solutions

Consider avoid using these keys for those calls

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

Effect observation approach

While still using the @observable API, the effect observation approach has more boilerplate and is convenient for instances where you want to observe one or more effects. Examples include when the user moves their mouse or other changes you might want to watch, independent of the component lifecycle.

HTML observation approach

Unlike other forms of observation, HTML observation is when you want to watch for changes to specific properties on elements, especially for web component properties.

The observer locator approach

The observer locator API allows you to observe properties for changes manually. In many instances, you will want to use @observer or @watch however, the observer locator can be useful in situations where you want to watch the properties of objects.

Observing property changes with @observable
Effect observation
HTML observation
Using observerLocator
<input pattern.attr="patternProp">
binding behavior
// getting the observer for property 'value'
const observer = observerLocator.getObserver(obj, 'value')
const subscriber = {
  handleChange(newValue) {
    console.log('new value of object is:', newValue)
  }
}

observer.subscribe(subscriber)

// and to stop subscribing
observer.unsubscribe(subscriber)
import { SVGAnalyzer } from '@aurelia/runtime-html';
import { Aurelia } from 'aurelia';

Aurelia
    .register(SVGAnalyzer) // <-- add this line
    ...
here
and use the appropriate file extension depending on whether you are using TypeScript or not. So,
hello-name.ts
or
hello-name.js
.

Notice how our new component doesn't look much different than the generated one? That's intentional. We are going to bind to this name property using two-way binding from within our view. We don't need any callback functions to update this value either.

Nice one! You just created a custom element view-model. It might not look like much, but you just learned a very core fundamental concept of building Aurelia applications. A view-model can be as simple and complex as you want it to be.

In the next chapter, we will hook this up with our view and allow a text input to change this value.

export class MyApp {
  message = 'Hello World!';
}
<div class="message">${message}</div>
export class HelloName {
  name = 'Person';
}
value is changed, the
colorChanged
callback will be fired. The new changed value will be the first argument, and the existing one will be the second one.

If you prefer, you can also put the @observable decorator on classes:

You do not have to check if newValue and oldValue are different. The change handler will not be called if you assign a value the property already has.

Specifying a different change callback

If you do not want to use the convention, you can define the callback name for the change handler by setting the callback property of the @observable decorator:

import { observable } from 'aurelia';

export class Car {
  @observable color = 'blue';

  colorChanged(newValue, oldValue) {
    // this will fire whenever the 'color' property changes
  }
}
@watch decorator
change
on the element. Another example is the
value
property of a
<select />
element should be observed by listening to the
change
event on it.

By default, the observation of HTML elements is done using a default node observer locator implementation. This default locator has a basic set of APIs that allows users to teach Aurelia how to observe HTML element observation effectively.

The following is the trimmed interface of the node observer locator, highlighting its capability to learn how to observe HTML elements:

useConfig and useConfigGlobal are two methods that can be used to teach the default node observer locator what events can be used to observe a property of a specific element or any element.

Node observer examples

Using the nodeObserverLocator API, we can tell Aurelia how to observe properties of HTML elements for changes. Under the hood, Aurelia already observes properties like values on form inputs, but it is good to understand how this functionality works, especially for custom elements and web components.

How to teach Aurelia to observe the value property of a <textarea /> element:

In this example, the eventsConfig argument has the value { events: ['input', 'change']}.

How to teach Aurelia to observe property length of an <input /> element:

In this example, eventsConfig argument has the value { events: ['input']}.

How to teach Aurelia observe property scrollTop of all elements:

In this example, eventsConfig argument has the value { events: ['scroll']}.

Observing custom elements in Web Components

It should be the same as observing custom (HTML) elements and normal HTML elements. It is common for Web Components to have well-defined events associated with their custom properties, so observing them often means adding a few configuration lines.

An example of how to teach Aurelia to observe the value property of a <my-input /> element, and <my-input /> dispatches valueChanged event when its value has been changed:

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.

Specifying a viewport on a route

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.

<main>
    <au-viewport name="main"></au-viewport>
</main>
<aside>
    <au-viewport name="sidebar"></au-viewport>
</aside>
import { IRouteableComponent, routes } from '@aurelia/router';

@routes([
    {
        component: import('./my-component'),
        path: 'my-component',
        title: 'Your Component <3',
        viewport: 'sidebar'
    }
])
export class MyApp implements IRouteableComponent {
}
Name of the key being resolved

Error explanation

Cyclic dependencies found. This means that you have tried including a dependency in your application that is trying to include the dependency you're loading. **** This happens when there is a dependency graph that looks like this: A --> B --> A or A --> B --> C --> A

Possible solutions

Check your code and extract what in A that causes the cyclic dependencies into a separate file, and refer to that from both A and B

You can also use a getter and static inject to work around this issue:

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

class Circular {
  get inject() { return [ICircularDep] }
}
<input pattern.bind="patternProp & attr">
import { observable } from 'aurelia';

@observable('color')
@observable({ name: 'speed', callback: 'speedCallback' })
export class Car {
  color = 'blue';
  speed = 300;

  colorChanged(newValue, oldValue) {
    // this will fire whenever the 'color' property changes
  }

  speedCallback(newValue, oldValue) {
    // this will fire whenever the 'speed' property changes
  }
}
import { observable } from 'aurelia';

export class Car {
  @observable({ callback: 'myCallback' }) color = 'blue';

  myCallback(newValue, oldValue) {
    // this will fire whenever the 'color' property changes
  }
}
export class NodeObserverLocator {
  allowDirtyCheck: boolean;
  handles(obj, key, requestor): boolean;

  useConfig(config): void;
  useConfig(nodeName, key, eventsConfig): void;

  useConfigGlobal(config): void;
  useConfigGlobal(key, eventsConfig): void;
}
nodeObserverLocator.useConfig('textarea', 'value', { events: ['input', 'change'] });
nodeObserverLocator.useConfig('input', 'length', { events: ['input'] });
nodeObserverLocator.useConfigGlobal('scrollTop', { events: ['scroll'] });
nodeObserverLocator.useConfig('my-input', 'value', { events: ['valueChanged'] });

Your first component - part 2: the view

Use a declarative approach to describe how your component should be rendered.

In the previous section, we created a basic view model, and you are probably sceptical that we are now going to create a view with a text input that updates this value without writing any more Javascript.

Inside the src directory create a new file called hello-name.html this will complicate the view model you already have (hello-name.ts or hello-name.js).

Firstly, let's write the code to display the value. Notice how we are using interpolation to print the value like the default generated my-app.html file was? ${name} the value inside the curly braces references the class property we defined in the previous section, by the name of name.

I would say run the app and see it in action, but we haven't imported our custom element just yet. Let's do that now.

Inside of my-app.html replace the entire file with the following:

We use the import element to include our newly created component. Take note there is a lack of file extension. This is because Aurelia will know to include our view-model and, in turn, include our view and any styles.

We then reference our custom element by its name. Aurelia knows using default conventions to take the file name, strip the file extension and use that as the tag name (this is configurable but beyond the scope of this tutorial).

If you were to run the app using npm start you would then see your application rendering your new component. You should see the text, Hello, Person! rendering in the view. Once you have the app running, leave it running as any changes you make will automatically be detected and re-rendered.

Binding the name to a text input

We have a functional custom element, but we promised we would be able to update the name with any value we want. Inside of hello-name.html add the following beneath the existing heading.

You should now see a text input with the value Person inside of it. By adding value.bind to the input, Aurelia will use two-way binding by default. This means it will show the value from the view-model inside of the view, and if it changes in either view or view-model, it will be updated. Think of a two-way binding as syncing the value.

Type something into the text input and you should see the heading value change as you type. This works because Aurelia binds to the native value property of the text input and keeps track of the changes for you without needing to use callbacks or anything else.

As you might have noticed, there really is not that much code here. Leveraging Aurelia's conventions and defaults allows you to write Aurelia applications without the verbosity and without writing tens of lines of code to do things like binding or printing values.

Route Events

The router emits several events via the Event Aggregator, which allows you to listen to router events as they occur. 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 that relate 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 of 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.

Resolvers

Getting started

Internally, the DI Container figures out how to locate each dependency using a Resolver. Which resolver is used depends on if or how the dependency was registered. (See below for ) However, you can specify a special resolver when you declare your dependencies.

For example, perhaps your application has an Inspector composed of Panel instances. You may want to inject the panels into the inspector. However, there isn't just one Panel; multiple different types of panels implement the Panel interface. In this case, you'd want to use the all resolver. Here's what that would look like with a plain class:

And with a decorator...

Portalling elements

An element in two places at once.

There are situations that some elements of a custom element should be rendered at a different location within the document, usually at the bottom of a document body or even inside of another element entirely. Aurelia supports this intuitively with the portal custom attribute.

While the location of the rendered element changes, it retains its current binding context. A great use for the portal attribute is when you want to ensure an element is displayed in the proper stacking order without needing to use CSS hacks like z-index:9999

Using the portal attribute without any configuration options will portal the element to beneath the document body (before the closing body tag).

0001 to 0015

Errors 0001 to 0015 are Dependency Injection errors.

Please see below a reference to each related error with explanations and resources for debugging and solving.

Markdown integration

One of the interesting features of Aurelia 2 is the use of different file types such as Markdown as View along with HTML. To do this, follow the steps below:

Create a skeleton with Webpack as a bundler and add markdown-loader configuration at the end of therules as following:

For this setting to work, you need to install the related package as well.

As a final step, we need to introduce the .md files into the TypeScript so find the resource.d.ts under src folder and append the following code into it.

AUR0011

Invalid resolver returned from the static register method

Error message

Invalid resolver returned from the static register method

Error explanation

Bundlers

A short intro guide that introduces different configuration for using different bundlers in Aurelia applications.

Webpack

Vite

This means the internal state of the Internal Resolver has been modified, into an invalid value**.**

Possible solutions

Check the register method on the key.

Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

  • AUR0004

  • AUR0005

  • AUR0006

  • AUR0007

  • AUR0008

  • AUR0009

  • AUR0010

  • AUR0011

  • AUR0012

  • AUR0013

  • AUR0014

  • AUR0015

  • Dependency Injection
    AUR0001
    AUR0002
    AUR0003
    <div>
        <h4>Hello, ${name}!</h4>
    </div>
    import { IEventAggregator } from 'aurelia';
    import { IRouteableComponent } from '@aurelia/router'; 
    
    export class MyComponent implements IRouteableComponent {    
        constructor(@IEventAggregator readonly ea: IEventAggregator) {
    
        }
        
        bound() {
            this.ea.subscribe('au:router:navigation-start', payload => {
                // Do stuff inside of this callback
            });
        }
     }   
    In another scenario, you might depend on a service that is expensive to create, which isn't always required, depending on what the user is doing with the app. In this case, you may want to resolve the dependency when it's needed lazily. For this, use the lazy resolver:

    And with a decorator...

    In this case, the lazy resolver will inject a function that you can call on demand to get an instance of the ExpensiveToCreateService.

    The core DI container does not enable circular dependencies. To get around this issue, in situations where you might have a circular reference, you can use a lazy resolver.

    In each of the resolvers above, the "key" we are providing in the inject property/decorator is different in type than the constructor parameter. As a result, we cannot use compiler-generated decorator metadata to provide the dependency information automatically. In the case of lazy, the compiler will see the type as Function while with all it will see it as Array. Neither provides enough information for the DI container to figure out what to do.

    Custom resolvers

    You can create your own resolver by implementing the IResolver interface. Here's what that looks like:

    As you can see, you only need to implement one method, which uses the container (either the original requesting container or the one that the request bubbled up to, if they aren't the same) to return the instance. Here's a simplified version of the all built-in function:

    And here's a simplified version of lazy:

    Note You can additionally implement getFactory to return a factory capable of creating instances of what this resolver will resolve. This is implemented by the standard internal resolver so that it's always possible to get the factory for singleton/transient registrations.

    registration information
    Targeting CSS selectors

    If you want to choose where a portalled element is moved to, you can supply a CSS selector where it should be moved.

    Target an element with an ID of somewhere:

    Target an element by class:

    Target an element by tagName:

    Targeting elements

    The portal attribute can also reference other elements with a ref attribute on them.

    You can also target elements not using the ref attribute too. A good example is a custom element. Maybe you want to portal a section of a child element to the parent element.

    We can do things with the injected element instance, like access the parentElement or other non-standard scenarios you might encounter.

    You could also do this with query calls such as querySelector and so forth as well aliased to class properties.

    Determining the position

    By default, the portal attribute will portal your elements before the closing tag of your target. By default using portal without any configuration values will portal it just before the closing </body> tag.

    We can override this behavior using the position property and the following values:

    • beforebegin

    • afterbegin

    • beforeend (the default value)

    • afterend

    In this example, our element will move to just after the opening body tag <body> the other values are self-explanatory.

    Installing

    For the latest stable version:

    For our nightly builds:

    Usage

    In vite.config.js:

    For apps in TypeScript, an extra typing definition is required for html module. You can add following file to your typing folder.

    Note that this is generated by default by the aurelia cli.

    html.d.ts

    Dev config

    By default, the aurelia vite plugin generates aliases to dev packages for better experience during development. It'll detect development mode based on the mode config from vite. You can also override using useDev options, in case there needs to be clarity into some behavior of the applications:

    npm i -D @aurelia/vite-plugin
    npm i -D @aurelia/vite-plugin
    <import from="./hello-name"></import>
    
    <hello-name></hello-name>
    <div>
        <h4>Hello, ${name}!</h4>
        
        <p><input type="text" value.bind="name"></p>
    </div>
    import { all } from 'aurelia';
    
    export class Inspector {
      static inject = [all(Panel)];
      constructor(private panels: Panel[]) { }
    }
    import { all } from 'aurelia';
    
    @inject(all(Panel))
    export class Inspector {
      constructor(private panels: Panel[]) { }
    }
    import { lazy } from 'aurelia';
    
    export class MyClass {
      static inject = [lazy(ExpensiveToCreateService)];
      constructor(private getExpensiveService: () => ExpensiveToCreateService) {}
    }
    import { lazy } from 'aurelia';
    
    @inject(lazy(ExpensiveToCreateService))
    export class MyClass {
      constructor(private getExpensiveService: () => ExpensiveToCreateService) {}
    }
    export interface IResolver<T = any> {
      resolve(handler: IContainer, requestor: IContainer): T;
      getFactory?(container: IContainer): IFactory<T> | null;
    }
    function all(key) {
      return {
        resolve(handler: IContainer, requestor: IContainer) {
          return requestor.getAll(key);
        }
      }
    }
    function lazy(key) {
      return {
        resolve(handler: IContainer, requestor: IContainer) {
          let lazyInstance = null;
    
          return () => {
            if (lazyInstance === null) {
              lazyInstance = requestor.get(key);
            }
    
            return lazyInstance;
          };
        }
      }
    }
    <div portal>My markup moves to beneath the body by default</div>
    <div portal="#somewhere">My markup moves toto DIV with ID somewhere</div>
    
    <div id="somewhere"><!-- The element will be portalled here --></div>
    <div portal=".somewhere">My markup moves to DIV with class somewhere</div>
    
    <div class="somewhere"><!-- The element will be portalled here --></div>
    <div portal="body">My markup moves to beneath the body (just before the closing tag)</div>
    <div portal="target.bind: somewhereElement">My markup moves to beneath the body</div>
    
    <div ref="somewhereElement"><!-- The element will be portalled here --></div>
    import { INode } from 'aurelia';
    
    export class MyComponent {
        constructor(@INode readonly element: HTMLElement) {}
    }
    <div>
        <div class="header" portal="target.bind: element.parentElement"></div>
    </div>
    <div portal="target: body; position: afterbegin;">My markup moves to beneath the body by default</div>
    import { defineConfig } from 'vite';
    import aurelia from '@aurelia/vite-plugin';
    
    export default defineConfig({
      ...,
      plugins: [aurelia()],
    });
    declare module '*.html' {
      import { IContainer } from '@aurelia/kernel';
      import { BindableDefinition } from '@aurelia/runtime';
      export const name: string;
      export const template: string;
      export default template;
      export const dependencies: string[];
      export const containerless: boolean | undefined;
      export const bindables: Record<string, BindableDefinition>;
      export const shadowOptions: { mode: 'open' | 'closed'} | undefined;
      export function register(container: IContainer);
    }
    import { defineConfig } from 'vite';
    import aurelia from '@aurelia/vite-plugin';
    
    export default defineConfig({
      ...,
      plugins: [aurelia({ useDev: true })], // always use dev bundles
    });
    Now, you are able to defineMarkdownfiles for your views and use them beside HTML files.
    // webpack.config.js
    
    // ...
    module: {
      rules: [
        // ...
        { test: /\.md$/i, use: ['@aurelia/webpack-loader', 'markdown-loader'], exclude: /node_modules/ }
      ]
    },
    // ...
    npm i markdown-loader -D
    declare module '*.md' {
      import { IContainer, PartialBindableDefinition } from 'aurelia';
      export const name: string;
      export const template: string;
      export default template;
      export const dependencies: string[];
      export const containerless: boolean | undefined;
      export const bindables: Record<string, PartialBindableDefinition>;
      export const shadowOptions: { mode: 'open' | 'closed' } | undefined;
      export function register(container: IContainer);
    }

    CSS classes and styling

    Learn how to style elements, components and other facets of an Aurelia application using classes and CSS. Strategies for different approaches are discussed in this section.

    Aurelia makes it easy to modify an element inline class list and styles. You can work with not only strings but also objects to manipulate elements.

    Binding HTML Classes

    The class binding allows you to bind one or more classes to an element and its native class attribute.

    Binding to a single class

    Adding or removing a single class value from an element can be done using the .class binding. By prefixing the .class binding with the name of the class you want to display conditionally selected.class="myBool" you can add a selected class to an element. The value you pass into this binding is a boolean value (either true or false), if it is true the class will be added; otherwise, it will be removed.

    Inside of your view model, you would specify isSelected as a property and depending on the value, the class would be added or removed.

    Here is a working example of a boolean value being toggled using .class bindings.

    Binding to multiple classes

    Unlike singular class binding, you cannot use the .class binding syntax to conditionally bind multiple CSS classes. However, there is a multitude of different ways in which this can be achieved.

    Syntax
    Input Type
    Example

    Once you have your CSS imported and ready to use in your components, there might be instances where you want to dynamically bind to the style attribute on an element (think setting dynamic widths or backgrounds).

    Binding Inline Styles

    Binding to a single style

    You can dynamically add a CSS style value to an element using the .style binding in Aurelia.

    Inside of your view model, you would specify bg as a string value on your class.

    Here is a working example of a style binding setting the background colour to blue:

    Binding to multiple styles

    To bind to one or more CSS style properties you can either use a string containing your style values (including dynamic values) or an object containing styles.

    Style binding using strings

    This is what a style string looks like, notice the interpolation here? It almost resembles just a plain native style attribute, with exception of the interpolation for certain values. Notice how you can also mix normal styles with interpolation as well?

    You can also bind a string from your view model to the style property instead of inline string assignment by using style.bind="myString" where myString is a string of styles inside of your view model.

    Style binding using objects

    Styles can be passed into an element by binding to the styles property and using .bind to pass in an object of style properties. We can rewrite the above example to use style objects.

    From a styling perspective, both examples above do the same thing. However, we are passing in an object and binding it to the style property instead of a string.

    Task Queue

    Not to be confused with task queue in Aurelia 1, the TaskQueue is a sophisticated scheduler designed to prevent a variety of timing issues, memory leaks, race conditions and more bad things that tend to result from setTimeout, setInterval, floating promises, etc.

    The benefit of using the task queue is both synchronous and asynchronous tasks are supported.

    We highly recommend using the task queue to replace existing uses of setTimeout and setInterval for better-performing applications.

    setTimeout (synchronous)

    In the following example, we use the delay configuration property to configure a task with a timeout of 100 milliseconds. This would replace using setTimeout in your applications.

    If you were to use a native setTimout it would look like this:

    Now, in your unit/integration/e2e tests or other components, you can await PLATFORM.taskQueue.yield() to deterministically wait for the task to be done (and not a millisecond longer than needed) or even PLATFORM.taskQueue.flush() to immediately run all queued tasks.

    Result: no more flaky tests or flaky code in general. No more intermittent and hard-to-debug failures.

    setTimeout (asynchronous)

    We performed a synchronous equivalent of a setTimeout. Now we can go one step further and do an asynchronous setTimeout, minus the floating promises and memory leaks.

    setInterval

    By supply the persistent configuration value, we can specify a task will remain and not be cleared by the task queue. This gives us setInterval functionality.

    requestAnimationFrame

    By leveraging the domWriteQueue we can also replace requestAnimationFrame with a safer alternative.

    requestAnimationFrame (loop)

    In situations where requestAnimationFrame is being used in a loop capacity (such as high frame rate animations), we can loop. The queue greatly simplifies this again with the persistent configuration option we saw above.

    Router events

    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.

    Emitted events

    The router emits the following events.

    • au:router:location-change: Emitted when the browser location is changed via the and 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.

    Subscribing to the events

    The events can be subscribed to using the . 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.

    Logging

    Aurelia provides a powerful logging API that allows you to display debug and error messages in your applications in a controlled manner.

    Getting started

    Aurelia comes with a flexible and powerful logging system that allows you to display debug and error messages in your applications in a slightly better way than using native console.log statements.

    Reasons to use logging inside of your apps and plugins include helpful debug messages for other developers (living comments) or displaying helpful information to the end-user in the console when something goes wrong.

    The logger is injected using dependency injection into your components:

    In this example, we scope our logger to our component. But scoping is optional, and the logger can be used without using scopeTohowever, we highly recommend using the scoping feature to group your messages in the console.

    Logging methods

    Just like console.log the Aurelia logger supports the following methods:

    • debug

    • info

    • warn

    • trace

    These methods are called on the logger instance you injected into your component.

    Just like console.log you can also pass in values such as strings, booleans, arrays and objects.

    Creating a logger

    To create a custom logger for your applications and plugins, you can create a more reusable wrapper around the logging APIs to use in your applications.

    It might look like a lot of code, but this logger implementation will create a scoped logger wrapper you can import into your applications and use in the following way:

    App configuration and startup

    Application Startup

    Aurelia allows you to configure the application startup in a couple of different ways. A quick setup where some defaults are assumed and a verbose setup where you can configure some of the framework-specific behaviors.

    Quick startup

    Dynamic composition

    In this section, we will learn how you can dynamically render components in your applications by utilizing Aurelia's dynamic composition functionality.

    When using Aurelia's <au-compose> element, inside of the view model being used, you have access to all of Aurelia's standard view lifecycle events, as well as an extra activate.

    The <au-compose> element allows us to compose view/view model pairs and just views, like a custom element, without specifying a tag name.

    Event Aggregator

    The Event Aggregator is a pub/sub-event package that allows you to publish and subscribe to custom events inside your Aurelia applications. Some parts of the Aurelia framework use the event aggregator to publish certain events at various stages of the lifecycle and actions taking place.

    The Event Aggregator is not for listening to native events. For those, you still use addEventListener and detachEventListener the event aggregator is a pub/sub package for publishing and subscribing to custom events.

    Navigation model

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

    Create a navigation menu using a navigation model

    Router Recipes

    While the docs do a great job explaining the intricacies of the router, sometimes you just need a code snippet and brief explanation to do something. You will find code snippets for basic things, from creating routes to working with router hooks.

    Create a routeable component

    A component that is loaded as part of a route definition. The IRouteableComponent

    Effect observation

    Aurelia provides a higher-level API for simplifying some common tasks to handle a common reactivity intent in any application: run a function again when any of its dependencies have been changed.

    This function is called an effect, and the dependencies are typically(1) tracked when they are accessed (read) inside this effect function. The builtin @observable decorator from Aurelia enables this track-on-read capability by default.

    Aurelia provides a few ways to declare a dependency for an effect function. The most common one is the track "on read" of a reactive property.

    In the following example:

    The property coord of a MouseTracker instance will be turned into a reactive property and is also aware of effect function dependency tracking.

    AUR0006

    Unable to autoregister dependency: [yyyy]

    Error message

    Unable to autoregister dependency: [yyyy]

    my-app.css
    my-app.html or my-app.md
    my-app.ts
    import { ILogger } from 'aurelia';
    
    export class MyComponent {
      public constructor(@ILogger private readonly logger: ILogger) {
          this.logger = logger.scopeTo('MyComponent');
      }
    }
    Parameters

    list of registering parameters

    Error explanation

    This means during the registration of some value with a container, it has reached the depth 100, which is an extreme case, and is considered invalid.

    Possible solutions

    Check your dependency graph, if it's really complex, which could happen over time, maybe inject a container and resolve the dependencies lazily instead, where possible.

    Please also note that this error could be caused by a plugin and not your application. After ruling out that the error is not being caused by your code, try removing any registered plugins one at a time to see if the error resolves itself.

    log.debug(`Debug message`);
    log.warn(`This is a warning`);

    The quick startup approach is what most developers will choose.

    Verbose Startup

    To start an Aurelia application, create a new Aurelia() object with a target host and a root component and call start().

    In most instances, you will not use the verbose approach to starting your Aurelia applications. The verbose approach is more aimed at developers integrating Aurelia into existing web applications and views.

    Register a globally available custom element

    Registering a single element

    To make a custom element globally available to your application, pass the custom element constructor to the .register() method on your Aurelia app.

    Register a set of elements

    If you have a package that exports all your custom elements, you can pass the entire package to the .register() method on your Aurelia app.

    src/components/index.ts:

    src/main.ts:

    Basic Composition

    The au-compose element can be used to render any custom element given to its component property. A basic example is:

    With a custom element as a view model, all standard lifecycles, and activate will be called during the composition.

    Composing Without a Custom Element

    Composing using a custom element definition is not always necessary or convenient. The au-compose can also work with a slightly simpler composition: either using view only or view and simple view model combination.

    An example of template-only composition:

    Inside our template, we use the <au-compose> element and pass through a view to be rendered. The view is just a plain HTML string.

    During a composition, this HTML string is processed by the Aurelia template compiler to produce necessary parts for UI composition and renders it inside the <au-compose> element.

    Combining simple view and literal object as view model, we can also have powerful rendering without boilerplate:

    When composing without a custom element as view model, the result component will use the parent scope as its scope unless scope-behavior is set to scoped

    Passing through data

    activate method on the view model, regardless of whether a custom element or a plain object is provided, will be called during the first composition and subsequent changes of the model property on the <au-compose> element.

    You can also pass an object inline from the template too:

    Inside the component view model being composed, the activate method will receive the object as its first argument.

    Accessing the view model

    In some scenarios, you may want to access the view model of the component being rendered using <au-compose> we can achieve this by adding the view-model.ref binding to our compose element.

    This will add a property to the host class called myCompose

    However, one pitfall you will encounter is the view model that gets passed to the ref binding is a constructible component and not the instance itself. If you worked with Aurelia 1, you might expect the passed view-model instance to be the instance itself, not the class definition.

    To access the instance itself, we need to reference the composition controller:

    We can now do calling methods inside our composed view model and other tasks you might need to accomplish for composed components.

    Migrating from Aurelia 1 <compose>

    The composition in Aurelia 2 is fundamentally different than Aurelia 1. The same ease of use is still there, but the way in which some things worked in v1 does not work the same in v2.

    Template and component-breaking changes

    1. In aurelia 2, view and view-model properties have been renamed to template and component respectively.

      If you were having view.bind or view-model.bind, change them to template.bind or component.bind respectively.

    2. In Aurelia 2, passing a string to the view or view-model properties no longer means module name. In Aurelia 1, the module would be resolved to a file. In v2, the view property only understands string values, and the view-model property only understands objects and classes.

    If you still want a view supporting a dynamically loaded module, you can create a value converter that achieves this.

    The above value converter will load the URL and return the text response. For view models, something similar can be achieved where an object or class can be returned.

    Scope breaking changes

    By default, when composing, the outer scope will not be inherited. The parent scope will only be inherited when it is not a custom element being composed. This means the outer scope will be used when composing only a view or plain object as the view model.

    You can disable this behavior using the scope-behavior attribute.

    Possible values are:

    • auto: in view only composition: inherit the parent scope

    • scoped: never inherit parent scope even in view only composition

    Using the event aggregator

    To use the Event Aggregator, we inject the IEventAggregator interface into our component. We inject it as IEventAggregator on our component class in the following code example.

    Subscribing to events

    The Event Aggregator provides a subscription method to subscribe to published events.

    Sometimes, you might only want to subscribe to an event once. To do that, we can use the subscribeOnce method to listen to the event and dispose of itself once it has been fired.

    Publishing events

    To publish (emit) an event, we use the publish method. You can provide an object to the publish method, which allows you to emit data via the event (accessible as a parameter on the subscribe method).

    Disposing of event listeners

    It's considered best practice to dispose of your event listeners when you are finished with them. Inside a component, you would usually do this inside of the unbinding method. The event will be of type IDisposable that we will use to type our class property strongly.

    Always clean up after yourself. Like native Javascript events, you should dispose of any events properly to avoid memory leaks and other potential performance issues in your Aurelia applications.

    Creating a route

    As outlined in the Creating Routes section, routes can be specified using the routes decorator or the static routes property.

    Creating a route with parameters

    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):

    Creating a route with custom configuration values

    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.

    Creating a route with a custom viewport

    In applications with multiple viewports, some routes might be loaded into specific viewports. You can use the viewport property on routes to specify which route.

    Loading data inside of a routeable component

    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.

    Redirect away from a component

    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 if the user is redirected away.

    Creating an Effect

    The effect API is provided via the default implementation of an interface named IObservation.

    An example to retrieve an instance of this interface is per following:

    Getting from a container directly

    Getting through injection

    Autoinjection (if you are using TypeScript)

    After getting ahold of an IObservation instance, an effect can be created via the method run of it:

    Note that the effect function will be run immediately.

    By default, an effect is independent of any application lifecycle, which means it does not stop when the application that owns the observation instance has stopped. To stop/destroy an effect, call the method stop() on the effect object:

    Effect Observation & Reaction Examples

    Creating an effect that logs the user mouse movement on the document

    Now whenever the user moves the mouse around, a log will be added to the console with the coordinate of the mouse.

    Creating an effect that sends a request whenever user focus/unfocus the browser tab

    import { PLATFORM } from 'aurelia';
    
    // Queue
    const task = PLATFORM.taskQueue.queueTask(() => {
      doStuff();
    }, { delay: 100 });
    
    // Cancel
    task.cancel();
    // Queue
    const handle = setTimeout(() => {
      doStuff();
    }, 100);
    
    // Cancel
    clearTimeout(handle);
    import { PLATFORM } from 'aurelia';
    
    // Queue
    const task = PLATFORM.taskQueue.queueTask(async () => {
      await doAsyncStuff();
    }, { delay: 100 });
    
    // Await
    await task.result;
    
    // Cancel
    task.cancel();
    import { PLATFORM } from 'aurelia';
    
    // Queue
    const task = PLATFORM.taskQueue.queueTask(() => {
      poll();
    }, { delay: 100, persistent: true /* runs until canceled */ });
    
    // Stop
    task.cancel();
    import { PLATFORM } from 'aurelia';
    
    PLATFORM.domWriteQueue.queueTask(() => {
      applyStyles();
    });
    // Start
    const task = PLATFORM.domWriteQueue.queueTask(() => {
      updateAnimationProps();
    }, { persistent: true });
    // Stop
    task.cancel();
    import { ILogger } from 'aurelia';
    
    export class MyComponent {
      public constructor(@ILogger private readonly logger: ILogger) {
          this.logger = logger.scopeTo('MyComponent');
      }
      
      public add() {
          this.logger.debug(`Adding something`);
      }
    }
    import { ILogger } from 'aurelia';
    
    export class MyComponent {
      public constructor(@ILogger private readonly logger: ILogger) {
          this.logger = logger.scopeTo('MyComponent');
      }
      
      public add() {
          this.logger.debug(`Adding something`, [
              { prop: 'value', something: 'else' }
          ]);
      }
    }
    import { DI, ILogger, ConsoleSink, IPlatform, LogLevel, LoggerConfiguration, Registration } from '@aurelia/kernel';
    import { BrowserPlatform } from '@aurelia/platform-browser';
    
    const PLATFORM = BrowserPlatform.getOrCreate(globalThis);
    
    const staticContainer = DI.createContainer();
    staticContainer.register(Registration.instance(IPlatform, Registration));
    staticContainer.register(LoggerConfiguration.create({ sinks: [ConsoleSink], level: LogLevel.fatal }));
    
    export const log = staticContainer.get(ILogger).scopeTo('My App');
    
    import { RouterConfiguration } from '@aurelia/router';
    import Aurelia, { StyleConfiguration } from 'aurelia';
    
    import { MyRootComponent } from './my-root-component';
    
    // By default host to element name (<my-root-component> for MyRootComponent),
    // or <body> if <my-root-component> is absent.
    Aurelia.app(MyRootComponent).start();
    
    // Or load additional Aurelia features
    Aurelia
      .register(
        RouterConfiguration.customize({ useUrlFragmentHash: false })
      )
      .app(MyRootComponent)
      .start();
    
    // Or host to <my-start-tag>
    Aurelia
      .register(
        RouterConfiguration.customize({ useUrlFragmentHash: false })
      )
      .app({
        component: MyRootComponent,
        host: document.querySelector('my-start-tag')
      })
      .start();
    import Aurelia, { StandardConfiguration } from 'aurelia';
    import { ShellComponent } from './shell';
    
    new Aurelia()
      .register(StandardConfiguration)
      .app({ host: document.querySelector('body'), component: ShellComponent })
      .start();
    import { CardCustomElement } from './components/card';
    
    // When using quick startup
    Aurelia
      .register(...)
      .register(<any>CardCustomElement);
      .app({ ... })
      .start();
    
    // When using verbose startup
    new Aurelia()
      .register(...)
      .register(<any>CardCustomElement)
      .app({ ... })
      .start();
    export { CardCustomElement } from './card';
    export { CollapseCustomElement } from './collapse';
    import * as globalComponents from './components';
    
    // When using quick startup
    Aurelia
      .register(...)
      .register(<any>globalComponents)
      .app({ ... })
      .start();
    
    // When using verbose startup
    new Aurelia()
      .register(...)
      .register(<any>globalComponents)
      .app({ ... })
      .start();
    my-app.html
    <au-compose component.bind="MyField"></au-compose>
    my-component.ts
    import { CustomElement } from '@aurelia/runtime-html';
    
    export class App {
      MyField = CustomElement.define({
        name: 'my-input',
        template: '<input value.bind="value">'
      })
    }
    <au-compose template="<p>Hello world</p>"></au-compose>
    <au-compose repeat.for="i of 5" component.bind="{ value: i }" template="<div>\\${value}</div>"></au-compose>
    <au-compose model.bind="myObject"></au-compose>
    <au-compose model.bind="{myProp: 'value', test: 'something'}"></au-compose>
    export class MyComponent {
        activate(model) {
            // Model contains the passed in model object
        }
    }
    <au-compose view-model.ref="myCompose"></au-compose>
    export class MyApp {
        readonly myCompose;
    }
    export class MyApp {
        readonly myCompose;
        myViewModel;
        
        constructor() {
            this.myViewModel = this.myCompose.composition.controller.viewModel;
        }
    }
    my-component.html
      <au-compose template="https://my-server.com/templates/${componentName} | loadTemplate">
    load-template.ts
      class LoadTemplateValueConverter {
        toView(v) { return fetch(v).then(r => r.text()) }
      }
      <au-compose scope-behavior="scoped">
    import { ICustomElementViewModel, IEventAggregator } from 'aurelia';
    
    export class MyComponent implements ICustomElementViewModel {    
        constructor(@IEventAggregator readonly ea: IEventAggregator) {
    
        }
     }   
    import { ICustomElementViewModel, IEventAggregator } from 'aurelia';
    
    export class MyComponent implements ICustomElementViewModel {    
        constructor(@IEventAggregator readonly ea: IEventAggregator) {
    
        }
        
        bound() {
            this.ea.subscribe('event name', payload => {
                // Do stuff inside of this callback
            });
        }
     }   
    import { ICustomElementViewModel, IEventAggregator } from 'aurelia';
    
    export class MyComponent implements ICustomElementViewModel {    
        constructor(@IEventAggregator readonly ea: IEventAggregator) {
    
        }
        
        bound() {
            this.ea.subscribeOnce('event name', payload => {
                // Do stuff inside of this callback just once
            });
        }
     }   
    import { ICustomElementViewModel, IEventAggregator } from 'aurelia';
    
    export class MyComponent implements ICustomElementViewModel {    
        constructor(@IEventAggregator readonly ea: IEventAggregator) {
    
        }
        
        bound() {
            const payload = {
                component: 'my-component',
                prop: 'value',
                child: {
                    prop: 'value'
                }
            };
            
            this.ea.publish('component bound', payload);
        }
     }   
    import { ICustomElementViewModel, IEventAggregator, IDisposable } from 'aurelia';
    
    export class MyComponent implements ICustomElementViewModel {    
        private myEvent: IDisposable;
        
        constructor(@IEventAggregator readonly ea: IEventAggregator) {
    
        }
        
        bound() {
            this.myEvent = this.ea.subscribe('event name', payload => {
                // Do stuff inside of this callback
            });
        }
        
        unbinding() {
            this.myEvent.dispose();
        }
     }   
    import { IRouteableComponent } from '@aurelia/router';
    
    export class MyComponent implements IRouteableComponent {
    
    }
    export class MyApp {
        static routes = [
            {
                path: '/products',
                component: () => import('./products-page'),
                title: 'Products'
            }
        ];
    }
    export class MyApp {
        static routes = [
            {
                path: '/products/view/:productId',
                component: () => import('./view-product'),
                title: 'Products'
            }
        ];
    }
    export class MyApp {
        static routes = [
            {
                path: '/products/view/:productId/:section',
                component: () => import('./view-product'),
                title: 'Products'
            }
        ];
    }
    export class MyApp {
        static routes = [
            {
                path: '/products/view/:productId',
                component: () => import('./view-product'),
                title: 'Products',
                data: {
                    icon: 'fa-light fa-alicorn'
                }
            }
        ];
    }
    export class MyApp {
        static routes = [
            {
                path: '/products/view/:productId',
                component: () => import('./view-product'),
                title: 'Products',
                viewport: 'sidebar'
            }
        ];
    }
    import { IRouteableComponent } from '@aurelia/router';
    
    export class ViewProduct implements IRouteableComponent {
        async canLoad(params) {
            this.product = this.api.loadProduct(params.productId);
        }
    }
    import { IRouteableComponent } from '@aurelia/router';
    
    export class ViewProduct implements IRouteableComponent {
        async canLoad(params) {
            return '/products';
        }
    }
    class MouseTracker {
      @observable
      coord = [0, 0];
    }
     import { IObservation } from 'aurelia';
    
     ...
     const observation = someContainer.get(IObservation);
     import { inject, IObservation } from 'aurelia';
    
     @inject(IObservation)
     class MyElement {
       constructor(observation) {
         // ...
       }
     }
     class MyElement {
       constructor(@IObservation readonly observation) {
         // ...
       }
     }
    const effect = observation.run(() => {
      // code here
    });
    const effect = IObservation.run(() => {
      // code here
    });
    
    // stop the effect like this
    effect.stop();
     import { inject, IObservation, observable } from 'aurelia'
    
     class MouseTracker {
       @observable coord = [0, 0]; // x: 0, y: 0 is the default value
     }
    
     // Inside an application:
     @inject(IObservation)
     class App {
       constructor(observation) {
         const mouseTracker = new MouseTracker();
    
         document.addEventListener('mousemove', (e) => {
           mouseTracker.coord = [e.pageX, e.pageY]
         });
    
         observation.run(() => {
           console.log(mouseTracker.coord)
         });
       }
     }
     import { inject, IObservation, observable } from 'aurelia'
    
     class PageActivity {
       @observable active = false
     }
    
     // Inside an application:
     @inject(IObservation)
     class App {
       constructor(observation) {
         const pageActivity = new PageActivity();
    
         document.addEventListener(visibilityChange, (e) => {
           pageActivity.active = !document.hidden;
         });
    
         observation.run(() => {
           fetch('my-game/user-activity', { body: JSON.stringify({ active: pageActivity.active }) })
         });
       }
     }
    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.

    Using the isActive property

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

    Excluding routes from the navigation model

    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.

    Disabling navigation model

    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.

    isActive

    class.bind="someString"

    string

    'col-md-4 bg-${bgColor}'

    class="${someString}"

    string

    col-md-4 ${someString}

    popstate
    hashchange
    event aggregator

    Lambda Expressions

    Remove boilerplate from your applications with template lambda expressions.

    In Aurelia applications, you might eventually encounter a situation where you need to filter an array using repeat.for or other situations where a lambda can make your application closer to conventional Javascript.

    Aurelia templates support a subset of Arrow Function syntax, allowing you to remove boilerplate and create concise applications where value converters or functions defined in your view models might have been previously used.

    Array methods with repeat.for

    Most likely, one of the most common scenarios will be calling Array methods on an array you're looping over using repeat.for.

    Previously, to filter some array items in a repeater, you might have written something like this using a value converter:

    While there is nothing wrong with value converters, and in some situations, they might be preferable (especially for testing), you can achieve the same thing without writing any additional code like this:

    We are calling a callback function called isGood defined inside of our template to determine if the item is filtered or not.

    Filter and Sort

    Observation-wise, Aurelia knows to only observe selected property of every item in items, as well as pos property of every selected item. This means changing the value of selected property of any item will result in the re-evaluation of the above expression. Changing the value of pos property of any selected item will also trigger the re-evaluation. Aurelia will also subscribe to the mutation of the array items to refresh this binding.

    Methods on array that will create an array subscription

    • map

    • filter

    Like we might have inside of a value converter, you can see we use two Javascript functions filter and sort — Aurelia's lamba expression support means we can chain these functions without needing to write any code in a view-model or value converter.

    Event Callbacks

    With arrow functions, we can express the following:

    As the following:

    As a result .call is being deprecated in Aurelia as the lambda expression syntax allows us to handle this in a more JavaScript way.

    Interpolation Expressions

    Not only are lambda functions supported in a repeat.for but we can also use them in interpolation expressions.

    Map

    Say you have an array of keywords for an item, and you wanted to display those as a comma-separated list. Previously, you would have used a value converter or function in your view-model to achieve this task. Now, you can do it from within your templates.

    Reduce

    Another task might be to take an array of items (say products in a cart) and then calculate the total. Once again, we might have used a value converter or computed getter for this task previously, but now we can use reduce in our template.

    Valid Uses

    While a broad syntax for lambda expressions is supported, here is a list of valid uses.

    Invalid Uses

    The following uses of lambda expressions are not supported in Aurelia templates.

    Enhance

    Learn how to use Aurelia with existing HTML (inside of other frameworks and libraries), hydrating server-generated HTML or run multiple instances of Aurelia in your application.

    An introduction to enhance

    The startup sections showed how to start Aurelia for an empty root node. While that's the most frequent use case, there might be other scenarios where we would like to work with an existing DOM tree with Aurelia.

    This includes pages partially rendered from a server with nodes and attributes representing Aurelia custom elements, custom attributes, or template controllers.

    Another example can be where you need to add a DOM fragment on the fly to the HTML document, and then you want Aurelia to take care of the bindings in that DOM fragment. This is commonly known as enhancing, where Aurelia takes a normal DOM fragment and associates behaviors with it.

    The basic syntax of enhance closely matches that of the normal startup.

    There are a few important points to note here.

    1. Every enhancement is treated as an anonymous custom element hydration, where the node being enhanced is the only element inside this anonymous element template.

    2. The component passed into Aurelia.enhance (MyComponent in our example above) can be a custom element class, an instance of a class, or an object literal. If it's a class, it will be instantiated by a container.\

      This container can be either specified by property container in the enhancement config object, or a new one will be created for this enhancement. @inject works like normal view model instantiation.

    That's it. Those are the main differences between enhance and the normal empty-root startup. Those two are the same in every other aspect because once a node is enhanced, all the data bindings or change handling will work like a normal Aurelia-hydrated empty-root node.

    Enhance application startup

    As discussed, the enhance API allows us to enhance our Aurelia applications in a few different ways. One method of enhancement is during application startup. This is convenient when you want to use Aurelia within an existing page (you might be using another framework or library or want to enhance a portion of a page).

    Let's create an index.html file which contains some HTML markup you would encounter if you generated an Aurelia application using npx makes aurelia or by other means.

    Pay attention to the contents of the <body> as there is a container called app as well as some HTML tags <my-app>

    Now, let's enhance our application by using the enhance API inside of main.ts

    We first wrap our async code in an anonymous async function. This allows us to catch errors and throw them to the console if enhancement fails but take note of the enhance call itself. We supply the container (in our case, it's a DIV with an ID of app as the host).

    Above our enhance call, we register our main component, which is MyApp which is the initial component our application will render.

    Pay attention to what you are enhancing. Please make sure you are enhancing the container and not the component itself. It's an easy mistake to make that we have seen some developers get caught on.

    This approach will work for existing applications as well. Say you have a WordPress website and want to create an Aurelia application on a specific page. You could create a container and then pass the element to host on the enhance call to do so.

    Enhancing on the fly within components

    While using the enhance During registration, the enhance API is convenient for situations where you want to control how an Aurelia application is enhanced. There are times when you want to enhance HTML from within components programmatically. This may be HTML loaded from the server or elements created on the fly.

    In the following example, we query our markup for an element called item-list and insert some Aurelia-specific markup into it (a repeater).

    As you can see, we are dynamically injecting some HTML into an existing element. Because this is being done after Aurelia has already compiled the view, it would not work. This is why we have to call enhance to tell Aurelia to parse our inserted HTML.

    You can use this approach to enhance HTML inserted into the page after initial compilation, perfect for server-generated code.

    Navigating

    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.

    Programmatically using the load method

    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.

    Navigating without options

    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:

    Specifying load options

    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:

    HTML load attribute

    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:

    Redirecting

    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 section to learn how to implement redirection inside your components.

    Router animation

    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.

    animation-hooks.ts
    import { lifecycleHooks } from '@aurelia/runtime-html';
    import anime from 'animejs';
    
    const animateIn = (element) =>
      anime({
        targets: element,
        translateX: () => ['110%', '0%'],
        duration: 900,
        easing: 'easeInOutQuart',
      });
    
    const animateOut = (element) =>
      anime({
        targets: element,
        translateX: () => ['0%', '110%'],
        duration: 900,
        easing: 'easeInOutQuart',
      });
    
    @lifecycleHooks()
    export class AnimationHooks {
      private element;
      private backwards = false;
    
      public created(vm, controller): void {
        this.element = controller.host;
      }
    
      public loading(vm, _params, _instruction, navigation) {
        this.backwards = navigation.navigation.back;
      }
      
      public unloading(vm, _instruction, navigation) {
        this.backwards = navigation.navigation.back;
      }
    
      public attaching() {
        if (this.backwards) {
          animateOut(this.element);
        } else {
          animateIn(this.element);
        }
      }
    
      public detaching() {
        if (this.backwards) {
          animateIn(this.element);
        } else {
          animateOut(this.element);
        }
      }
    }

    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:

    App Tasks

    App tasks provide injection points to run code at certain points of the compiler lifecycle allowing you to interface with different parts of the framework and execute code.

    Falling somewhere in between component lifecycles and lifecycle hooks, app tasks offer injection points into Aurelia applications that occur at certain points of the compiler lifecycle. Think of them as higher-level framework hooks.

    The app task API has the following calls:

    • creating — Runs just before the root component is created by DI - last chance to register deps that must be injected into the root

    • hydrating — Runs after instantiating the root view, but before compiling itself, and instantiating the child elements inside it - good chance for a router to do some initial work

    • hydrated — Runs after self-hydration of the root controller, but before hydrating the child element inside - good chance for a router to do some initial work

    • activating — Runs right before the root component is activated - in this phase, scope hierarchy is formed, and bindings are getting bound

    • activated — Runs right after the root component is activated - the app is now running

    • deactivating — Runs right before the root component is deactivated - in this phase, scope hierarchy is unlinked, and bindings are getting unbound

    • deactivated — Runs right after the root component is deactivated

    You register the app tasks with the container during the instantiation of Aurelia or within a plugin (which Aurelia instantiates). In fact, there are many examples of using app tasks throughout the documentation. Such as , , and the section on using the .

    Many of Aurelia's own plugins use app tasks to perform operations involving registering numerous components and asynchronous parts of the framework.

    Asynchronous app tasks

    A good example of where app tasks can come in handy is plugins that need to register things with the DI container. The app task methods can accept a callback, but also a key and callback, which can be asynchronous.

    In the above example, we await importing a file which could be a JSON file or something else inside the task itself. Then we register it with DI.

    Another great example of using app tasks is the dialog plugin that comes with Aurelia. The deactivating task is used to close all modals using the dialog service, as you can see .

    Registering app tasks

    In Aurelia applications, app tasks are registered through the register method and will be handled inside of your main.ts file.

    Within a plugin, the code is similar. You would be exporting a function which accepts the DI container as its first argument, allowing you to register tasks and other resources.

    An app task example

    Using code taken from a real Aurelia 2 application, we will show you how you might use App Tasks in your Aurelia applications. One such example is Google Analytics.

    We then pass the GoogleAnalyticsTask constant and register it with the container inside main.ts

    The above code runs during the activating app task and register/attach the Google Analytics SDK to our application.

    Animation

    A developer guide that details numerous strategies for implementing animation into Aurelia applications.

    Learn numerous techniques for implementing animations into your Aurelia applications.

    Component-based animations

    In instances where you don't need to implement router-based transition animations (entering and leaving), we can lean on traditional CSS-based animations to add animation to our Aurelia applications.

    Let's animate the disabled state of a button by making it wiggle when we click on it:

    export class MyApp {
        private disabled = false;
        
        animateButton() {
            this.disabled = true;
            
            setTimeout(() => {
                this.disabled = false;
            }, 2000);
        }
    }

    Stateful Animations

    Some animations are reactive based on user input or other application actions. An example might be a mousemove event changing the background colour of an element.

    In this example, when the user moves their mouse over the DIV, we get the clientX value and feed it to a reactive style string that uses the x value to complete the HSL color value. We use lower percentages for the other values to keep the background dark for our white text.

    Reactive Animations

    Not to be confused with state animations, a reactive animation is where we respond to changes in our view models instead of views and animate accordingly. You might use an animation library or custom animation code in these instances.

    In the following example, we will use the animation engine Anime.js to animate numeric values when a slider input is changed. Using the change event on our range slider, we'll animate the number up and down depending on the dragged value.

    Strongly-typed templates

    Many users may be familiar with libraries such as FAST Element or lit-html . They want to know how to write strongly-typed templates with Aurelia.

    In the following, we will see how to write a template as follows and introduce it to Aurelia.

    export const buttonTemplate = html<BootstrapButton>`
        <button class="btn btn-primary btn-${x => x.size} ${x => x.block ? 'btn-block' : ''}" ref="bsButtonTemplate">
            ${(x) => x.getName()}
        </button>
    `;

    To do this, we need two simple pieces of functionality so, create a strongly-typed-template file.

    The idea behind the code is really simple. First, we separate strings and variables parts inside html function.

    string(s):

    <button class="btn btn-primary btn-lg" ref="atButtonTemplate">
    
    </button>

    variable(s):

    x => x.size
    
    x => x.block ? 'btn-block' : ''
    
    (x) => x.getName()

    Then, for variable parts, we remove the lambda part (VARIABLE => VARIABLE.) by regex via parse function. Finally, an HTML is created according to the acceptable standards for the Aurelia template engine.

    The generic parameter in this function is actually your view-model.

    Now, we have to introduce this strongly-typed template to Aurelia via template option.

    CSS-in-JS with Emotion

    What is CSS-in-JS?

    CSS-in-JS is a styling technique where JavaScript is used to style components. When this JavaScript is parsed, CSS is generated (usually as an <style> element) and attached to the DOM. It allows you to abstract CSS to the component level, using JavaScript to describe styles in a declarative and maintainable way.

    This method is very common in the ecosystem of some client-side libraries, such as React. So we decided to discuss how to use CSS-in-JS in Aurelia.

    Why EmotionJS?

    There are multiple implementations of CSS-in-JS concept in the form of libraries, but few of them are framework-agnostic, so we chose

    Emotion, as described on its site, says:

    Emotion is a performant and flexible CSS-in-JS library. Building on many other CSS-in-JS libraries, it allows you to style apps quickly with string or object styles. It has a predictable composition to avoid specificity issues with CSS. With source maps and labels, Emotion has a great developer experience and great performance with heavy caching in production.

    EmotionJS & Aurelia 2 integration

    To integrate EmotionJS and Aurelia, Follow the steps below: Add EmotionJS framework-agnostic version to your project with the following command

    Define a custom attribute and name it Emotion, just like the following code

    What is isInShadow? This method helps us to find out if our HTMLElement is inside of a shadow-root or not.

    Why does shadow-root matter? Because Aurelia 2 supports ShadowDOM, we need to style those HTMLElements inside a shadow via the emotion library.

    What is cache.sheet.container? The emotion library uses container configuration to inject styles into specific DOM. To support shadow-root, we should inject our styles into the shadow block but for global styles document.head is good.

    Why did we choose attached? Detecting ShadowDOM mode for an HTMLElement is possible via this life-cycle method.

    Now, Register the new Emotion custom attribute in your main.ts file.

    Add an object in your view model and call it cssObject.

    Go to your view and add emotion custom attribute to an HTML tag.

    Securing an app

    Introduction

    The first rule of securing client-side applications is: the client cannot be trusted. Your backend should never trust the input from the front end under any circumstance. Malicious individuals often know how to use browser debug tools and manually craft HTTP requests to your backend.

    You may even find yourself in a situation where a disgruntled employee (or former employee), an engineer with intimate knowledge of the system, is seeking revenge by attempting a malicious attack.

    Your primary mechanism for securing any SPA application, Aurelia or otherwise, is to work hard at securing your backend services.

    Security Advice

    This article only contains a few basic tips. It is in no way exhaustive, nor should it be your only resource for securing your application. The bulk of the security work you will set up in your application falls on your server-side technology. You should spend adequate time reading up on and understanding security best practices for whatever backend tech you have chosen.

    Authentication and authorization

    When designing your application, consider which backend API calls can be made anonymously, which require a logged-in user, and which roles or permissions are required for various authenticated requests.

    Ensure that your entire API surface area is explicitly covered in this way. Your front end can facilitate the login process, but ultimately this is a backend task. Here are a few related recommendations:

    • Make sure your server is configured to transmit sensitive resources over HTTPS. You may want to transmit all resources this way. It is more server-intensive, but it will be more secure. You must decide what is appropriate for your application.

    • Don't transmit passwords in plain text.

    • There are various ways to accomplish CORS. Prefer to use a technique based on server-supported CORS rather than client-side hacks.

    You can improve the user experience by plugging into Aurelia's router pipeline with your security specifics. Again, remember, this doesn't secure your app but only provides a smooth user experience. The real security is on the backend.

    We have a working example to learn how to implement authorization checks using the router.

    Validation and sanitization

    The backend should always perform validation and sanitization of data. Do not rely on your client-side validation and sanitization code. Your client-side validation/sanitization code should not be seen as anything more than a User Experience enhancement designed to aid honest users. It will not affect anyone acting maliciously.

    Here are a few things you should do, though:

    • Use client-side validation. This will make your users happy.

    • Avoid data-binding to innerHTML. If you do, be sure to use a value converter to sanitize the input from the user.

    • Be extra careful anytime you are dynamically creating and compiling client-side templates based on user input.

    We Are Trying To Help You

    Internally, Aurelia makes no use of eval or the Function constructor. Additionally, all binding expressions are parsed by our strict parser, which does not make globals like window or document available in binding expressions. We've done this to help prevent some common abuses.

    Secret data

    Do not embed private keys into your JavaScript code. While the average user may not be able to access them, anyone with ill intent can download your client code, un-minify it and use basic regular expressions on the codebase to find things that look like sensitive data.

    Perhaps they've discovered what backend technology you are using or what cloud services your product is based on simply by studying your app's HTTP requests or looking at the page source. Using that information, they may refine their search based on certain patterns well-known to users of those technologies, making it easier to find your private keys.

    If you need to acquire any secret data on the client, it should be done with great care. Here is a (non-exhaustive) list of recommendations:

    • Always use HTTPS to transmit this information.

    • Restrict which users and roles can acquire this information to an absolute minimum.

    • Always use time-outs on any secret keys so that, at most, if an attacker gains access, they can't use them for long.

    • Be careful how you store these values in memory. Do not store these as class property values or on any object linked to the DOM through data-binding or otherwise. Doing so would allow an attacker to access the information through the debug console. If you must store this information, keep it inside a private (non-exported) module-level variable.

    Deployment

    When deploying your apps, there are a few things you can do to make it more difficult for attackers to figure out how your client works:

    • Bundle your application and minify it. This is the most basic obfuscation you can do.

    • Do not deploy the original client-side source files. Only deploy your bundled, minified app.

    • For additional security or IP protection, you may want to look into products such as .

    Prepare for the inevitable

    Even with the most skilled, security-proficient development team, your app will never be 100% protected. This is a fundamental assumption that you should have from the beginning. Expect to be attacked and expect someone to succeed at some point in time. What will you do if this happens? How will you respond? Will you be able to track down the culprit? Will you be able to identify the issue and quickly seal up the breach? You need a plan.

    Again, most of this comes down to server-side implementation. Here are a few basic ideas:

    • Configure server-side logging and make sure it will provide you with useful information. Such information can be very helpful in tracking down how an attack was performed. Make sure you have tools to quickly search through your logs.

    • Make sure that all logins are logged. If you use an auth-token scheme, ensure all requests log this information.

    • Never log sensitive data.

    Local templates (inline templates)

    Local templates allow you to remove boilerplate in your Aurelia applications, by creating local templates specific to the templated view you are working within and are not reusable.

    Introduction

    In many instances, when working with templated views in Aurelia, you will be approaching development from a reusability mindset. However, sometimes, you need a template for one specific application part. You could create a component for this, but it might be overkill. This is where local templates can be useful.

    The example defines a template inside the

    Route hooks

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

    Getting started

    Please note that in Aurelia2 there are two routers, namely 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,

    Value converters (pipes)

    Use value converters to transform how values are displayed in your applications. You can use value converters to transform strings, format dates, currency display and other forms of manipulation. They can be used within interpolation and bindings, working with data to and from the view.

    If you have worked with other libraries and frameworks, you might know value converters by another name; pipes.

    Understanding value converter data flow

    Most commonly, you'll be creating value converters that translate model data to a format suitable for the view; however, there are situations where you'll need to convert data from the view to a format expected by the view model, typically when using two-way binding with input elements.

    interface INavigationModel {
      /**
       * Collection of routes.
       */
      readonly routes: readonly {
        readonly id: string;
        readonly path: string[];
        readonly redirectTo: string | null;
        readonly title: string | ((node: RouteNode) => string | null) | null;
        readonly data: Record<string, unknown>;
        readonly isActive: boolean;
      }[];
    }
    import { INavigationModel, IRouteContext } from '@aurelia/router-lite';
    
    export class NavBar {
      private readonly navModel: INavigationModel;
      public constructor(@IRouteContext routeCtx: IRouteContext) {
        this.navModel = routeCtx.navigationModel;
      }
    }
    <nav style="display: flex; gap: 0.5rem;">
        <template repeat.for="route of navModel.routes">
            <a href.bind="route.path | firstNonEmpty">\${route.title}</a>
        </template>
    </nav>
    <style>
      .active {
        font-weight: bold;
      }
    </style>
    <nav>
      <template repeat.for="route of navModel.routes">
        <a href.bind="route.path | firstNonEmpty" active.class="route.isActive">${route.title}</a>
      </template>
    </nav>
    import { route } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    import { NotFound } from './not-found';
    
    @route({
      routes: [
        {
          path: ['', 'home'],
          component: Home,
          title: 'Home',
        },
        {
          path: 'about',
          component: About,
          title: 'About',
        },
        {
          path: 'notfound',
          component: NotFound,
          title: 'Not found',
          nav: false,       // <-- exclude from navigation model
        },
      ],
      fallback: 'notfound',
    })
    export class MyApp {}
    <p selected.class="isSelected">I am selected (I think)</p>
    <p background.style="bg">My background is blue</p>
    my-app.ts
    export class MyApp {
      private backgroundColor = 'black';
      private textColor = '#FFF';
    }
    my-app.html
    <p style="color: ${textColor}; font-weight: bold; background: ${backgroundColor};">Hello there</p>
    my-app.ts
    export class MyApp {
      private styleObject = {
        background: 'black',
        color: '#FFF'
      };
    }
    my-app.html
    <p style.bind="styleObject">Hello there</p>
    import {
      IRouterEvents,
      LocationChangeEvent,
      NavigationStartEvent,
      NavigationEndEvent,
      NavigationCancelEvent,
      NavigationErrorEvent,
    } from '@aurelia/router-lite';
    import { IDisposable } from 'aurelia';
    
    export class SomeService implements IDisposable {
      private readonly subscriptions: IDisposable[];
      public log: string[] = [];
      public constructor(@IRouterEvents events: IRouterEvents) {
        this.subscriptions = [
          events.subscribe('au:router:location-change',   (event: LocationChangeEvent)   => { /* handle event */ }),
          events.subscribe('au:router:navigation-start',  (event: NavigationStartEvent)  => { /* handle event */ }),
          events.subscribe('au:router:navigation-end',    (event: NavigationEndEvent)    => { /* handle event */ }),
          events.subscribe('au:router:navigation-cancel', (event: NavigationCancelEvent) => { /* handle event */ }),
          events.subscribe('au:router:navigation-error',  (event: NavigationErrorEvent)  => { /* handle event */ }),
        ];
      }
      public dispose(): void {
        const subscriptions = this.subscriptions;
        this.subscriptions.length = 0;
        const len = subscriptions.length;
        for (let i = 0; i < len; i++) {
          subscriptions[i].dispose();
        }
      }
    }
    import { IRouterEvents } from '@aurelia/router-lite';
    
    export class MyApp {
      private navigating: boolean = false;
      public constructor(@IRouterEvents events: IRouterEvents) {
        events.subscribe(
          'au:router:navigation-start',
          () => (this.navigating = true)
        );
        events.subscribe(
          'au:router:navigation-end',
          () => (this.navigating = false)
        );
      }
    }
    import { IRouter, IRouteableComponent } from '@aurelia/router';
    
    export class MyComponent implements IRouteableComponent {
        constructor(@IRouter private router: IRouter) {
    
        }
    }
    import { One } from './one';
    import { Two } from './two';
    import { AnimationHooks } from './animation-hooks';
    
    export class MyApp {
      static dependencies = [AnimationHooks];
    
      public static routes = [
        { path: ['', 'one'], component: One },
        { path: 'two', component: Two },
      ];
    
      public message: string = 'Hello Aurelia 2!';
    }pe
    @keyframes wiggle {
      0%, 7% {
        transform: rotateZ(0);
      }
      15% {
        transform: rotateZ(-15deg);
      }
      20% {
        transform: rotateZ(10deg);
      }
      25% {
        transform: rotateZ(-10deg);
      }
      30% {
        transform: rotateZ(6deg);
      }
      35% {
        transform: rotateZ(-4deg);
      }
      40%, 100% {
        transform: rotateZ(0);
      }
    }
    
    .wiggle {
      animation: wiggle 2s linear infinite;
    }
    <button type="button" wiggle.class="disabled" click.trigger="animateButton()">Wiggle!</button>
    strongly-typed-template.ts
    type TemplateValue<T> = { [P in keyof T]: T[P] extends Function ? never : P }[keyof T] | ((val: T) => unknown);;
    
    function parse(val: any) {
        const variable = val?.toString();
        const isFunc = variable.indexOf("f") > -1;
        if (!variable) return "";
        const firstParanthesis = variable.indexOf("(") + 1;
        const secondParanthesis = variable.indexOf(")");
        const variableName = variable.substring(firstParanthesis, secondParanthesis) || variable[0];
        const regex = new RegExp(`${variableName}\\.`, "g");
        return variable
            .substring(
                isFunc ? variable.indexOf("return") + 7 : variable.indexOf("=>") + 3
            )
            .replace(regex, "");
    }
    
    export const html = <TSource = any>(
        strings: TemplateStringsArray, // html text
        ...values: TemplateValue<TSource>[] // variables which they are functions.
    ) => {
        let html = "";
        for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
            const currentString = strings[i];
            const value = values[i];
            html += currentString;
            if (typeof value === "function") {
                const parsed = parse(value);
                html += `\${${parsed}}`;
                continue;
            }
            html += `\${${value}}`;
        }
    
        html += strings[strings.length - 1];
        return html;
    }
    Control cross-domain requests to your services. Either disallow them or configure your server using a strict allow list of allowed domains.
  • Require strong passwords

  • Never, ever store passwords in plain text.

  • Do not allow multiple failed login attempts to the same account.

  • Consider outsourcing your auth requirements to a cloud provider with greater expertise.

  • Be extra careful anytime you are dynamically creating templates on the server based on user input, which will later be processed by Aurelia on the client.
  • If you need to store this information anywhere, encrypt it first.

  • Consider timing out logins or auth tokens. You can provide refresh mechanisms to help the user experience.
  • Configure server insight tooling so that threats can be detected earlier.

  • here
    jscrambler

    includes

  • indexOf

  • lastIndexOf

  • findIndex

  • find

  • flat

  • flatMap

  • join

  • reduce

  • reduceRight

  • slice

  • some

  • Methods that trigger self mutation like sort/splice/push/pop/shift/unshift/reverse will not result in a subsription it's unclear when and how to refresh the binding.

    For sorting, it is recommended that we create a new array with slice before sorting: items.slice(0).sort(...) since sort() mutates the existing array and could sometimes make the outcome confusing to follow.

    The host is usually an existing non-enhanced (neither by .app nor by .enhance) DOM node. Note that .enhance does not detach or attach the host node to the DOM by itself. If the host is truly detached, it must be explicitly attached to the DOM. An important consequence is that if there are existing event handlers attached to the host node or one of its successor nodes, those stay as is.

  • The result of an enhance call is an activated custom element controller. This controller needs to be deactivated manually by the application or connected to an existing controller hierarchy to be deactivated automatically by the framework.\

    An example of enhancement result deactivation:

  • Routing Lifecycle
    MS Fast integration
    building plugins
    template compiler
    here
    bs-button-template.ts
    import { html } from './strongly-typed-template';
    
    // BootstrapButton is my view-model
    export const buttonTemplate = html<BootstrapButton>`
        <button class="btn btn-primary btn-${x => x.size} ${x => x.block ? 'btn-block' : ''}" ref="bsButtonTemplate">
            ${(x) => x.getName()}
        </button>
    `;
    bs-button.ts
    import { buttonTemplate } from "./bs-button-temlate";
    
    @customElement({ name: "bs-button", template: buttonTemplate /* HERE */ })
    export class BootstrapButton /* view-model */ {
        @bindable({ mode: BindingMode.toView }) public size: string;
        @bindable({ mode: BindingMode.toView }) public block: boolean;
        getName() {
            return "Primary Button";
        }
    }
    npm i emotion --save
    EmotionJS
    my-app.html
    markup that can be used as a custom element. The name of the custom element, so defined, comes from the value of the
    as-custom-element
    attribute used on the template.

    In this case, it is named as person-info. A custom element defined that way cannot be used outside the template that defines it; in this case, the person-info is, therefore, unavailable outside my-app. Thus, the name 'local template'.

    Local templates can also optionally specify bindable properties using the <bindable> tag as shown above. Apart from property, other allowed attributes that can be used in this tag are attribute, and mode. In that respect, the following two declarations are synonymous.

    Although it might be quite clear, it is worth reiterating that the value of the bindable attribute should not be camelCased or PascalCased.

    Why use local templates

    In essence, the local templates are similar to HTML-Only custom elements, with the difference that the local templates cannot be reused outside the defining custom element. Sometimes we need to reuse a template multiple times in a single custom element.

    Creating a separate custom element for that is a bit overkill. Also, given that the custom element is only used in one single custom element, it might be optimized for that and not meant to be reused outside this context. The local templates are meant to promote that, whereas having a separate custom element makes it open for reuse in another context.

    In short, it aims to reduce boilerplate code and promotes highly cohesive, better-encapsulated custom elements.

    This means that the following is a perfectly valid example. Note that the local templates with the same name (foo-bar) are defined in different custom elements.

    Features and pitfalls

    Like anything, there is always an upside and downside: local templates are no different. While they can be a powerful addition to your Aurelia applications, you need to be aware of the caveats when using them, as you may encounter them.

    Local templates are hoisted.

    It is theoretically possible to go to an infinite level of nesting. That is, the following example will work. However, whether such composition is helpful depends on the use case.

    Although it might provide a stronger cohesion, as the level of nesting grows, it might not be easy to work with. It is up to you to decide on a reasonable tradeoff while using local templates.

    In this respect, a good thumb rule is to keep the local function analogy in mind.

    Custom elements cannot contain only local templates

    The following examples will cause a (jit) compilation error.

    A local template always needs to be defined directly under the root element

    The following example will cause a (jit) compilation error.

    Local templates need to have a name

    The following example will cause a (jit) compilation error.

    Local template names need to be unique

    The following example will cause a (jit) compilation error.

    Bindable tags need to be under the local template root

    The following example will cause a (jit) compilation error.

    The property attribute on bindable tags is mandatory

    The following example will cause a (jit) compilation error.

    <template as-custom-element="person-info">
      <bindable property="person"></bindable>
      <div>
        <label>Name:</label>
        <span>${person.name}</span>
      </div>
      <div>
        <label>Address:</label>
        <span>${person.address}</span>
      </div>
    </template>
    
    <h2>Sleuths</h2>
    <person-info repeat.for="sleuth of sleuths" person.bind="sleuth"></person-info>
    
    <h2>Nemeses</h2>
    <person-info repeat.for="nemesis of nemeses" person.bind="nemesis"></person-info>
    export class App {
      public readonly sleuths: Person[] = [
        new Person('Byomkesh Bakshi', '66, Harrison Road'),
        new Person('Sherlock Holmes', '221b Baker Street')
      ];
      public readonly nemeses: Person[] = [
        new Person('Anukul Guha', 'unknown'),
        new Person('James Moriarty', 'unknown')
      ];
    }
    
    class Person {
      public constructor(
        public name: string,
        public address: string,
      ) { }
    }
    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:

    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.

    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.

    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.

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

    Lifecycle Hooks
    <div repeat.for="i of list | specialFilterBy">
    <div repeat.for="i of list.filter(item => isGood(item))">
    <div repeat.for="item of items.filter(x => x.selected).sort((a, b) => a.pos - b.pos)">
      ${item.name}
    </div>
    <my-input change.call="updateValue($event)">
    <my-button click.trigger="handleClick">
    <my-input change.bind="v => updateValue(v)">
    <my-button click.trigger="e => handleClick(e)">
    ${keywords.map(x => x.name).join(', ')})
    <p>Total: ${items.reduce((sum, product) => sum + product.price, 0)}</p>
    () => 42
    (a) => a
    (...a) => a[0]
    a => a
    (a, b) => `${a}${b}`
    (a, ...rest) => `${a}${rest.join('')}`
    a => b => a + b
    () => {} // no function body
    (a = 42) => a // no default parameters
    ([a]) => a // no destructuring parameters
    ({a}) => a // no destructuring parameters
    const au = new Aurelia();
    await au.enhance({ host, component: MyComponent });
    const controller = au.enhance({ host, component });
    
    controller.deactivate(controller, null, LifecycleFlags.none);
    index.html
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>Aurelia</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <base href="/" />
      </head>
    
      <body>
        <div id="app">
          <my-app></my-app>
        </div>
      </body>
    </html>
    import Aurelia from 'aurelia';
    import { MyApp } from './my-app';
    
    (async () => {
        await Aurelia
            .register(MyApp)
            .enhance({
                host: document.querySelector('div#app'),
                component: {},
             });
    })().catch(console.error);
    import Aurelia, { IAurelia } from 'aurelia';
    
    export class MyApp {
      items = [1, 2, 3];
    
      message = 'hello world!';
    
      // Inject an instance of Aurelia into our component
      constructor(@IAurelia private readonly au: Aurelia) {}
    
      attached() {
        const itemList = document.getElementById('item-list');
    
        itemList.innerHTML = "<div repeat.for='item of items'>${item}</div>";
    
        // Call the enhance API
        this.au.enhance({
          // The component is our view model, we specify an object with an array of items
          component: { items: this.items },
          // The element to enhance
          host: itemList,
        });
      }
    }
    import { IRouter, IRouteableComponent } from '@aurelia/router';
    
    export class MyComponent implements IRouteableComponent {
        constructor(@IRouter private router: IRouter) {
    
        }
    
        async viewProducts() {
            await this.router.load('/products');
        }
    }
    import { IRouter, IRouteableComponent } from '@aurelia/router';
    
    export class MyComponent implements IRouteableComponent {
        constructor(@IRouter private router: IRouter) {
    
        }
    
        async viewProducts() {
            await this.router.load(`/products/12`);
        }
    }
    import { IRouter, IRouteableComponent } from '@aurelia/router';
    
    export class MyComponent implements IRouteableComponent {
        constructor(@IRouter private router: IRouter) {
    
        }
    
        async viewProduct() {
            await this.router.load('products', {
                title: 'My product',
                parameters: {
                    prop1: 'val',
                    tracking: 'asdasdjaks232'
                },
                fragment: 'jfjdjf'
            });
        }
    }
    <a load="/products/12">Product #12</a>
    <a load="route:product;">My Route</a>
    <a load="route:profile; params.bind:{name: 'rob'}">View Profile</a>
    {
        id: 'profile',
        path: 'profile/:name',
        component: () => import('./view-profile'),
        title: 'View Profile'
    },
    main.ts
    import { IContainer } from '@aurelia/kernel';
    import { AppTask, DI, Registration } from 'aurelia';
    
    Aurelia.register(
        AppTask.hydrating(IContainer, async container => {
            if (config.enableSpecificOption) {
                const file = await import('file');
                cfg.register(Registration.instance(ISpecificOption, file.do());
            }
            
            Registration.instance(IBootstrapV5Options, config).register(container);
        })
    );
    main.ts
    import Aurelia, { AppTask } from 'aurelia';
    
    const au = new Aurelia();
    
    au.register(
        AppTask.activating(() => {
            console.log('actiating or before activate');
        })
    );
    export function register(container: IContainer) {
        container.register(
            AppTask.activating(() => {
                console.log('activating or before activate');
            })
        )
    }
    import { IGoogleAnalytics } from './../resources/services/google-analytics';
    import { AppTask } from 'aurelia';
    
    export const GoogleAnalyticsTask = AppTask.activating(IGoogleAnalytics, (ga) => {
        ga.init('UA-44935027-5');
        ga.attach();
    });
    Aurelia.register(GoogleAnalyticsTask);
    import { inject } from "aurelia";
    import { css, cache } from 'emotion'
    @inject(Element)
    export class EmotionCustomAttribute {
        constructor(private element: Element) {
        }
        attached() {
            if (this.isInShadow(this.element))
                cache.sheet.container = this.element.getRootNode() as HTMLElement;
            else
                cache.sheet.container = document.head;
            this.element.classList.add(css(this.value));
        }
        private isInShadow(element: Element): boolean {
            return element.getRootNode() instanceof ShadowRoot;
        }
    }
    import Aurelia from 'aurelia';
    import { MyApp } from './my-app';
    import { EmotionCustomAttribute } from './emotion-attr';
    Aurelia
      .register(EmotionCustomAttribute)
      .app(MyApp)
      .start();
    export class MyApp {
      public message = 'Hello World!';
      private color = 'white'
      public cssObject = {
        backgroundColor: 'hotpink !important',
        '&:hover': {
          color: this.color
        }
      };
    }
    <div class="message" emotion.bind="cssObject">${message}</div>
    <template as-custom-element="foo-bar">
      <bindable property='prop'></bindable>
    
      Level One ${prop}
    </template>
    <foo-bar prop.bind="prop"></foo-bar>
    class LevelOne {
      @bindable public prop: string;
    }
    <template as-custom-element="foo-bar">
      <bindable property='prop'></bindable>
      Level Two ${prop}
      <level-one prop="fiz baz"></level-one>
    </template>
    <foo-bar prop.bind="prop"></foo-bar>
    <level-one prop.bind="prop"></level-one>
    class LevelTwo {
      @bindable public prop: string;
    }
    <level-two prop="foo2"></level-two>
    <level-one prop="foo1"></level-one>
    <!--Having such custom element does not help much either.-->
    <template as-custom-element="foo-bar">Does this work?</template>
    <template as-custom-element="fiz-baz">Of course not!</template>
    <div>
      <template as-custom-element="foo-bar">This does not work.</template>
    </div>
    <bindable property="foo" mode="twoWay" attribute="fiz-baz"></bindable>
    @bindable({mode: BindingMode.twoWay, attribute: 'fiz-baz'}) foo;
    <foo-bar foo.bind="'John'"></foo-bar>
    
    <template as-custom-element="foo-bar">
      <bindable property="foo"></bindable>
      <div> ${foo} </div>
    </template>
    <template as-custom-element="el-one">
    <template as-custom-element="one-two">
      1
    </template>
    2
    <one-two></one-two>
    </template>
    <template as-custom-element="el-two">
    <template as-custom-element="two-two">
      3
    </template>
    4
    <two-two></two-two>
    </template>
    <el-two></el-two>
    <el-one></el-one>
    <template as-custom-element="">foo-bar</template>
    <div></div>
    <template as-custom-element="foo-bar">foo-bar1</template>
    <template as-custom-element="foo-bar">foo-bar2</template>
    <div></div>
    <template as-custom-element="foo-bar">
      <div>
        <bindable property="prop"></bindable>
      </div>
    </template>
    <div></div>
    <template as-custom-element="foo-bar">
      <bindable attribute="prop"></bindable>
    </template>
    <div></div>
    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();
    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);
    }
    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);
    }
    import { IRouteableComponent } from "@aurelia/router";
    import { AuthHook } from './route-hook';
    
    export class SettingsPage implements IRouteableComponent {
        static dependencies = [ AuthHook ];
    }
    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
    @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}`);
        }
    }
    to get Aurelia installed in minutes.

    Installation

    Configure the 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.

    Create the routable components

    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.

    Configure the routes

    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:

    Using pushstate

    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.

    Live examples

    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.

    @aurelia/router
    consult our Quick Start
    toView

    The toView method always receives the supplied value as the first argument, and subsequent parameters are configuration values (if applicable). This specifies what happens to values going to the view and allows you to modify them before display.

    fromView

    The fromView method always receives the supplied value as the first argument, and subsequent parameters are configuration values (if applicable). This specifies what happens to values going out of the view to the view model and allows you to modify them before the view model receives the changed value.

    Using value converters

    To apply a value converter, you use the pipe | character followed by the name of the value converter you want to use. If you have worked with Angular before, you would know value converters as pipes.

    While Aurelia itself comes with no prebuilt value converters, this is what using them looks like for an imaginary value converter that converts a string to lowercase.

    The code for this value converter might look something like this:

    Transforming data using multiple value converters and parameters

    Multiple value converters

    Value converters can be chained, meaning you can transform a value and then transform it through another value converter. To chain value converters, you separate your value converters using the pipe |. In this fictitious example, we are making our value lowercase and then running it through another value converter called bold which will wrap it in strong tags to make it bold.

    Parameter based value converters

    You can also create value converters that accept one or more parameters. For value converters that format data, you might want to allow the developer to specify what that format is for say a currency or date formatting value converter.

    Parameters are supplied using the colon : character, and like the pipe, for multiple parameters, you can chain them. Parameters can be supplied as one or more strings (passed through to the value converter method as one or more arguments) or a singular object.

    Static parameters

    Furthermore, value converter parameters also support bound values. Unlike other types of binding, you only have to supply the variable, which will bind it for you.

    Bound parameters

    Object parameters

    If your value converter is going to have a lot of parameters, the existing approaches will fall apart quite quickly. You can specify your value converters take a single object of one or more parameters. Object parameters will also let you name them, unlike other parameters.

    On our fromView and toView methods, the second argument will be the supplied object we can reference.

    Creating value converters

    Create custom value converters that allow you to format how your data is displayed and retrieved in your views.

    Like everything else in Aurelia, a value converter is a class. A value converter that doesn't actually do anything might look like this. Nothing about this example is Aurelia-specific and is valid in Javascript.

    Value converters are always referenced as camelCase when used in your templates.

    To teach you how value converters can be created, we will create a simplistic value converter called date, allowing us to format dates.

    In this example, we will use a decorator valueConverter to decorate our class. While you can use the ValueConverter naming convention as we did above, it's important to learn the different ways you can create value converters.

    Import your value converter in your view

    This example value below will display June 22, 2021 in your view. Because our default date format is US, it will display as month-date-year.

    The locale parameter that we specified in our value converter supports a locale parameter, which allows us to change how our dates are displayed. Say you're in the UK or Australia. The default format is then date-month-year.

    To see our value converter in action, here is what it looks like:

    export class MyApp {
        private x = 0;
        
        mouseMove(x) {
            this.x = x;
        }
    }
    .movetransition {
        padding: 20px;
        transition: 0.4s background-color easein-out;
    }

    Component lifecycles

    Every component instance has a lifecycle that you can tap into. This makes it easy for you to perform various actions at particular times.

    For example, you may want to execute some code as soon as your component properties are bound but before the component is first rendered. Or, you may want to run some code to manipulate the DOM as soon as possible after your element is attached to the document.

    Every lifecycle callback is optional. Implement whatever makes sense for your component, but don't feel obligated to implement any of them if they aren't needed for your scenario. Some of the lifecycle callbacks make sense to implement in pairs (binding/unbinding, attaching/detaching) to clean up any resources you have allocated.

    If you register a listener or subscriber in one callback, remember to remove it in the opposite callback. For example, a native event listener registered in attached should be removed in detached

    All arguments on these callback lifecycle methods are optional and, in most cases, will not be needed.

    Constructor

    When the framework instantiates a component, it calls your class's constructor, just like any JavaScript class. This is the best place to put your basic initialization code that is not dependent on bindable properties.

    Furthermore, the constructor is where you handle the injection of dependencies using dependency injection. You will learn about DI in the , but here is a basic example of where the constructor is used.

    Define

    The "define" hook is the go-to hook for dynamic contextual composition. It runs just after the constructor and can be treated like a late interceptor for the @customElement decorator / CustomElement.define api: it allows you to change the CustomElementDefinition created by the framework before it is compiled, as well as making certain changes to the controller (for example, wrapping or overriding the scope).

    You'll have the compiled definition of the parent (owning) element available and the custom element's hydration context. The returned definition is the cache key for the compiled definition.

    To make a change only the first time the hook is invoked for an instance underneath a particular parent definition (affecting all instances of the type underneath that parent definition), mutate and return the existing definition; to make a contextual change (that needs to be re-compiled per instance), clone the definition before mutating and returning it.

    Hydrating

    The "hydrating" hook allows you to add contextual DI registrations (to controller.container) to influence which resources are resolved when the template is compiled. It runs synchronously right after the define hook and is still considered part of "construction".

    From a caching perspective, it has a direct 1-1 parity with the define hook: the hydration is cached per a unique definition that is returned from define (or per parent definition, if no new definition is returned from define).

    Therefore, if you need true per-instance contextual registrations (which should be rare), make sure to bust the cache per instance by returning a clone from the define hook.

    Hydrated

    The "hydrated" hook is a good place to influence how child components are constructed and rendered contextually. It runs synchronously after the definition is compiled (which happens synchronously after hydrating) and, like hydrating, can still be considered part of "construction" and has a direct 1-1 parity with the define hook from a caching perspective.

    This is the last opportunity to add DI registrations specifically for child components in this container or any other way, affecting what is rendered and how it is rendered.

    Created

    The "created" hook is the last hook that can be considered part of "construction". It is called (synchronously) after this component is hydrated, which includes resolving, compiling and hydrating child components. In terms of the component hierarchy, the created hooks execute bottom-up, from child to parent (whereas define, hydrating and hydrated are all top-down). This is also the last hook that runs only once per instance.

    Here you can perform any last-minute work that requires having all child components hydrated, which might affect the bind and attach lifecycles.

    Binding

    If your component has a method named "binding", then the framework will invoke it after the bindable properties of your component are assigned. In terms of the component hierarchy, the binding hooks execute top-down, from parent to child, so your bindables will have their values set by the owning components, but the bindings in your view are not yet set.

    This is a good place to perform any work or change anything your view would depend on because data still flows down synchronously. This is the best time to do anything that might affect children. We prefer using this hook over bound, unless you specifically need bound for a situation when binding is too early.

    You can optionally return a Promise either making the method asynchronous or creating a promise object. If you do so, it will suspend the binding and attaching of the children until the promise is resolved. This is useful for fetching/save of data before rendering.

    Bound

    If your component has a method named "bound", then the framework will invoke it when the bindings between your component and its view have been set. This is the best place to do anything that requires the values from let, from-view or ref bindings to be set.

    Attaching

    If your component has a method named "attaching, " the framework will invoke it when it has attached the component's HTML element. You can queue animations and/or initialize certain 3rd party libraries.

    If you return a Promise from this method, it will not suspend the binding/attaching of child components, but it will be awaited before the attached hook is invoked.

    Attached

    If your component has a method named "attached", the framework will invoke it when it has attached the current component and all of its children. In terms of the component hierarchy, the attached hooks execute bottom-up.

    This is the best time to invoke code that requires measuring of elements or integrating a 3rd party JavaScript library that requires the whole component subtree to be mounted to the DOM.

    Detaching

    If your component has a method named "detaching", then the framework will invoke it when removing your HTML element from the document. In terms of the component hierarchy, the detaching hooks execute bottom-up.

    If you return a Promise (for example, from an outgoing animation), it will be awaited before the element is detached. It will run in parallel with promises returned from the detaching hooks of siblings/parents.

    Unbinding

    If your component has a method named "unbinding, " the framework will invoke it when it has fully removed your HTML element from the document. In terms of the component hierarchy, the unbinding hooks execute bottom-up.

    Dispose

    If your component has a method named "dispose", then the framework will invoke it when the component is to be cleared from memory completely. It may be called, for example, when a component is in a repeater, and some items are removed that are not returned to the cache.

    This is an advanced hook mostly useful for cleaning up resources and references that might cause memory leaks if never explicitly dereferenced.

    Lifecycle Hooks

    The lifecycle hooks API supports all of the above lifecycle methods. Using the lifecycleHooks decorator, you can perform actions at various points of the component lifecycle. Because the router uses lifecycle hooks, they are documented in the router section, but do not require the use of the router to use (except for router-specific hooks).

    Others

    For <au-compose>, there are extra lifecycle hooks that are activate/deactivate. Refers to for more details.

    Routing Lifecycle

    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.

    canLoad

    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.

    Required data

    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.

    Redirecting

    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.

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

    canUnload

    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.

    unloading

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

    Loading data inside of components

    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 loadinglifecycle callback.

    When you use load and async the component will wait for the data to load before rendering.

    Getting information about the currently active route

    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.

    Get current route details

    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.

    Get query parameters from the URL

    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.

    Setting the title from within components

    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.

    TailwindCSS integration

    Learn how to use TailwindCSS in Aurelia 2 with this detailed guide.

    What is Tailwind CSS?

    Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.

    for more information take a look at Tailwind CSS

    How to configure an Aurelia 2 project with Tailwind CSS?

    1- Run the following command in your terminal

    2- Use your type of project, I am using Default Typescript with Webpack and CSS.

    3- Install Tailwind CSS in your project via this command

    4- After installation go to the root folder and run the below command too

    This command will create a tailwind.config.js file in the root folder beside the webpack.config.js file with the following content

    5- Open your webpack.config.js file and add the below line into the postcssLoader literal object as a first item in plugins array. (Just like the picture)

    6- Add these lines to the top of your main CSS file (for example my-app.css)

    How to test it?

    In an easy way you can add the following Tailwind CSS snippet code to your project.

    I have added this to my-app.html now you can run the project by

    Seems everything works

    What is PurgeCSS?

    is a tool to remove unused CSS. It can be used as part of your development workflow. Purgecss comes with a JavaScript API, a CLI, and plugins for popular build tools.

    Why do we need PurgeCSS with Tailwind CSS?

    Purgecss is particularly effective with Tailwind because Tailwind generates thousands of utility classes for you, most of which you probably won't actually use. For more information, you can read .

    If you run the build command, you will see the final bundle side is huge (even in production mode)

    How can we enable PurgeCSS?

    Open the tailwind.config.js file and replace

    with

    Now, execute the build command again and see the result.

    List Rendering

    Learn how to work with collections of data like arrays and maps. The repeat.for functionality allows you to loop over collections of data and work with their contents similar to a Javascript for loop.

    Aurelia supports working with different types of data. Array, Set, and Map are all supported collection types that can be iterated in your templates.

    repeat.for

    You can use the repeat.for binding to iterate over data collections in your templates. Think of repeat.for as a for loop. It can iterate arrays, maps and sets.

    import { customElement } from '@aurelia/runtime-html';
    import template from './home.html';
    
    @customElement({ name: 'ho-me', template })
    export class Home {
      private readonly message: string = 'Welcome to Aurelia2 router-lite!';
    }
    <h1>${message}</h1>
    import { customElement } from '@aurelia/runtime-html';
    import template from './about.html';
    
    @customElement({ name: 'ab-out', template })
    export class About {
      private readonly message = 'Aurelia2 router-lite is simple';
    }
    <h1>${message}</h1>
    import { customElement } from '@aurelia/runtime-html';
    import { route } from '@aurelia/router-lite';
    import template from './my-app.html';
    import { Home } from './home';
    import { About } from './about';
    
    @route({
      routes: [
        {
          path: ['', 'home'],
          component: Home,
          title: 'Home',
        },
        {
          path: 'about',
          component: About,
          title: 'About',
        },
      ],
    })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
    
    <nav>
      <a href="home">Home</a>
      <a href="about">About</a>
    </nav>
    
    <au-viewport></au-viewport>
    npm i @aurelia/router-lite
    main.ts
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router-lite';
    import { MyApp } from './my-app';
    
    Aurelia
      .register(RouterConfiguration.customize({
          useUrlFragmentHash: true, // <-- enables the routing using the URL `hash`
        }))
      .app(MyApp)
      .start();
    <h1>${someValue | toLowercase}</h1>
    export class ToLowercaseValueConverter {
        toView(value) {
            return value.toLowerCase();
        }
    }
    <h1>${someValue | toLowercase | bold }</h1>
    <h1>${someValue | date:'en-UK'}
    <h1>${someValue | date:format}
    export class MyApp {
        format = 'en-US';
    }
    <ul>
        <li repeat.for="user of users | sort: { propertyName: 'age', direction: 'descending' }">${user.name}</li>
    </ul>
    export class ThingValueConverter {
      toView(value) {
        return value;
      }
    }
    date-value-converter.ts
    import { valueConverter } from 'aurelia';
    
    @valueConverter('date')
    export class FormatDate {
      toView(value: string, locale = 'en-US') {
        const date = new Date(value);
    
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
        const format = Intl.DateTimeFormat(locale, {
          month: 'long',
          day: 'numeric',
          year: 'numeric',
          timeZone: 'UTC'
        });
    
        if (Number.isNaN(date.valueOf())) {
          return 'Invalid Date';
        }
    
        return format.format(date);
      }
    }
    <import from="./date-value-converter" />
    <p>${'2021-06-22T09:21:26.699Z' | date}</p>
    <p>${'2021-06-22T09:21:26.699Z' | date:'en-GB'}</p>
    <div
      mousemove.trigger="mouseMove($event.clientX)"
      style="background-color: hsl(${x}, 40%, 32%)"
      class="movetransition"
    >
      <p>Move it, move it.</p>
      <p>X value is: ${x}</p>
    </div>
    import anime from 'animejs';
    
    export class MyApp {
      private sliderVal = 0;
      private sliderWrapper: HTMLElement;
    
      animateValue() {
        anime({
          targets: this.sliderWrapper,
          textContent: `${this.sliderVal}`,
          easing: 'easeInOutQuad',
          round: true,
          duration: 1200,
        });
      }
    }
    <input
      type="range"
      min="0"
      max="1000000"
      value.bind="sliderVal"
      change.trigger="animateValue()"
    />
    
    <p ref="sliderWrapper" class="slider-wrapper">${sliderVal & oneTime}</p>
    .slider-wrapper {
      background: #333;
      color: #fff;
      display: block;
      font-family: Arial, Helvetica, sans-serif;
      font-size: 19px;
      font-weight: bold;
      padding: 12px;
    }
    dependency injection section
    here
    dynamic composition doc
    Purgecss
    Controlling File Size
    Breaking this intuitive syntax down, it works like this:
    • Loop over every item in the items array

    • Store each iterative value in the local variable item on the left-hand side

    • For each iteration, make the current item available

    If you were to write the above in Javascript form, it would look like this:

    Index and Contextual properties inside of repeat.for

    Aurelia's binding engine makes several special properties available in your binding expressions. Some properties are available everywhere, while others are only available in a particular context.

    Below is a summary of the available contextual properties within repeats.

    $index

    In a repeat template, the item's index is in the collection. The index is zero-indexed, meaning the value starts from zero and increments by one for each iteration in the repeat.for loop. The following example $index will be 0 for the first iteration, then 1 and so forth.

    $first

    In a repeat template the $first variable will be true if this is the first iteration in the repeat.for loop. If the iteration is not the first iteration, then this value will be false.

    $last

    In a repeat template the $last variable will be true if this is the last iteration in the repeat.for loop. This means we have reached the final item in the collection we are iterating. If the loop is continuing, this value will be false.

    $even

    In a repeat template the $even variable will be true if we are currently at an even-numbered index inside the repeat.for loop. You would use this functionality when performing conditional logic (alternate row styling on table rows and so forth).

    $odd

    In a repeat template the $odd variable will be true if we are currently at an odd-numbered index inside the repeat.for loop. You would use this functionality when performing conditional logic (alternate row styling on table rows and so forth).

    $length

    In a repeat template the $length variable will provide the length of the collection being iterated. This value should not change throughout iterating over the collection and represents the length of the collection akin to (Array.length).

    $parent

    Explicitly accesses the outer scope from within a repeat.for template. In most instances where you are dealing with the value of the collection you are iterating, the $parent variable will not be needed.

    You may need this when a property on the current scope masks a property on the outer scope. Note that this property is chainable, e.g. $parent.$parent.foo is supported.

    Inside of the repeat.for these can be accessed. In the following example, we display the current index value.

    You would need this functionality when dealing with multiple levels of repeat.for aka nested repeaters. As each repeat.for creates its own scope, you need to use $parent to access the outer scope of the repeater.t

    Array Syntax

    In this section, see how you can iterate an array using repeat.for — you will notice the syntax is the same as the examples we used above, except for a view model containing the array to show you the relationship.

    Aurelia will not be able to observe changes to arrays using the array[index] = value syntax. To ensure that Aurelia can observe the changes on your array, use the Array methods: Array.prototype.push, Array.prototype.pop, and Array.prototype.splice.

    Ranges Syntax

    The repeat.for functionality doesn't just allow you to work with collections. It can be used to generate ranges.

    In the following example, we generate a range of numbers up to 10. We subtract the value from the index inside to create a reverse countdown.

    Set Syntax

    The repeat.for functionality works not only with arrays (the most collection type you will be using) but also with Javascript sets. The syntax for iterating sets is the same as it is for arrays, so nothing changes in the template, only the collection type you are working with.

    Map Syntax

    One of the more useful iterables is the Map because you can directly decompose your key and value into two variables in the repeater. Although you can repeat over objects straightforwardly, Maps can be two-way bound easier than Objects, so you should try to use Maps where possible.

    Please take note of the syntax in our template. Unlike repeat.for usage for Arrays and Sets, we are decomposing the key and value (as we discussed) on the repeater itself. The syntax is still familiar but slightly different.

    One thing to notice in the example above is the dereference operator in [greeting, friend] - which breaks apart the map's key-value pair into greeting, the key, and friend, the value. Note that because all of our values are objects with the name property set, we can get our friend's name with ${friend.name}, just as if we were getting it from JavaScript!

    Object Syntax

    In Javascript, objects are not a native collection type. However, there might be situations where you want to iterate values inside of an object. Aurelia does not provide a way of doing this, so we need to create a Value Converter to transform our object into an iterable format.

    Create a value converter

    We take the object in our view model, friends, and run it through our keys value converter. Aurelia looks for a registered class named KeysValueConverter and tries to call its toView() method with our friend's object. That method returns an array of keys- which we can iterate.

    Our value converter uses the Javascript Reflect API to get the keys of our object, returning the values in an iterable format that our repeat.for can understand. In our template, we import our value converter to use it.

    When creating utility value converters and other resources, we recommend globally registering them using Aurelia's DI. You can learn how to use Dependency Injection here.

    import { IRouter } from '@aurelia/router-lite';
    
    export class MyComponent {
        constructor(@IRouter readonly router: IRouter) {
        }
    }
    export class MyComponent {
        define(controller: IDryCustomElementController<this>, hydrationContext: IHydrationContext<unknown>, definition: CustomElementDefinition<Constructable<{}>>) {
    
        }
    }
    export class MyComponent {
        hydrating(controller: IContextualCustomElementController<this>) {
    
        }
    }
    export class MyComponent {
        hydrated(controller: IContextualCustomElementController<this>) {
    
        }
    }
    export class MyComponent {
        created(controller: IContextualCustomElementController<this>) {
    
        }
    }
    export class MyComponent {
        binding(initiator: IHydratedController, parent: IHydratedController, flags: LifecycleFlags) {
    
        }
    }
    export class MyComponent {
        bound(initiator: IHydratedController, parent: IHydratedController, flags: LifecycleFlags) {
    
        }
    }
    export class MyComponent {
        attaching(initiator: IHydratedController, parent: IHydratedController, flags: LifecycleFlags) {
    
        }
    }
    export class MyComponent {
        attached(initiator: IHydratedController, flags: LifecycleFlags) {
    
        }
    }
    export class MyComponent {
        detaching(initiator: IHydratedController, parent: IHydratedController, flags: LifecycleFlags) {
    
        }
    }
    export class MyComponent {
        unbinding(initiator: IHydratedController, parent: IHydratedController, flags: LifecycleFlags) {
    
        }
    }
    export class MyComponent {
        dispose() {
        }
    }
    import { IRouteableComponent, Parameters, Navigation, RoutingInstruction } from '@aurelia/router';
    
    export class MyComponent implements IRouteableComponent {
        canLoad(params: Parameters, instruction: RoutingInstruction, navigation: Navigation);
        loading(params: Parameters, instruction: RoutingInstruction, navigation: Navigation);
        canUnload(instruction: RoutingInstruction, navigation: Navigation);
        unloading(instruction: RoutingInstruction, navigation: Navigation);
    }
    import { IRouteableComponent, Parameters } from "@aurelia/router";
    
    export class MyProduct implements IRouteableComponent {
        canLoad(params: Parameters) {
            return true;
        }
    }
    import { IRouteableComponent, Parameters } from "@aurelia/router";
    
    export class MyProduct implements IRouteableComponent {
        async canLoad(params: Parameters) {
            await ....load data (Fetch call, etc)
        }
    }
    import { IRouteableComponent, Parameters } from "@aurelia/router";
    
    export class MyProduct implements IRouteableComponent {
        canLoad(params: Parameters) {
            return '/'; // Matches default empty route
        }
    }
    import { IRouteableComponent, Parameters } from "@aurelia/router";
    
    export class MyProduct implements IRouteableComponent {
        canLoad(params: Parameters) {
            return 'products'; // Matches route with ID 'products'
        }
    }
    import { IRouteableComponent, Parameters } from "@aurelia/router";
    
    export class MyProduct implements IRouteableComponent {
        canLoad(params: Parameters) {
            return '/products/54'; // Matches route path for product/:productId
        }
    }
    import { IRouteableComponent, Parameters } from "@aurelia/router";
    
    export class MyComponent implements IRouteableComponent {
        async canLoad(params: Parameters) {
            this.product = await this.api.getProduct(params.productId);
        }
    }
    import { IRouteableComponent, Parameters } from "@aurelia/router";
    
    export class MyComponent implements IRouteableComponent {
        async loading(params: Parameters) {
            this.product = await this.api.getProduct(params.productId);
        }
    }
    import { IRouteableComponent, IRouter, Navigation, Parameters, RoutingInstruction } from '@aurelia/router';
    
    loading(parameters: Parameters, instruction: RoutingInstruction, navigation: Navigation): void | Promise<void> {
        console.log(instruction.endpoint.instance.getContent().instruction);
    }
    loading() {
        console.log(this.router.activeNavigation.parameters);
    }
    import { IRouteableComponent, Parameters, RoutingInstruction, Navigation } from "@aurelia/router";
    
    export class ProductPage implements IRouteableComponent {
      loading(parameters: Parameters, instruction: RoutingInstruction, navigation: Navigation) {
        instruction.route.match.title = 'COOL BEANS';
      }
    }
    npx makes aurelia
    npm i tailwindcss -D
    or
    yarn add tailwindcss -D
    ./node_modules/.bin/tailwind init
    module.exports = {
      purge: [],
      theme: {
        extend: {},
      },
      variants: {},
      plugins: [],
    }
    require('tailwindcss')('tailwind.config.js'),
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    <div class="p-6">
        <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
          <strong class="font-bold">Holy smokes!</strong>
          <span class="block sm:inline">Something seriously bad happened.</span>
          <span class="absolute top-0 bottom-0 right-0 px-4 py-3">
            <svg class="fill-current h-6 w-6 text-red-500" role="button" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><title>Close</title><path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"/></svg>
          </span>
        </div>
    </div>
    npm run start
    or
    yarn run
    npm run build
    or
    yarn build
    purge: [],
    purge: {
      enabled: true,
      content: ['./src/**/*.html'],
    },
    <ul>
        <li repeat.for="item of items">${item.name}</li>
    </ul>
    for (let item of items) {
        console.log(item.name);
    }
    <ul>
        <li repeat.for="item of items">${$index}</li>
    </ul>
    <ul>
        <li repeat.for="item of items">${$first}</li>
    </ul>
    <ul>
        <li repeat.for="item of items">${$last}</li>
    </ul>
    <ul>
        <li repeat.for="item of items">${$even}</li>
    </ul>
    <ul>
        <li repeat.for="item of items">${$odd}</li>
    </ul>
    <ul>
        <li repeat.for="item of items">${$length}</li>
    </ul>
    <ul>
        <li repeat.for="item of items">${$parent.$index}</li>
    </ul>
    my-component.ts
    class MyComponent {
      items = [
        {name: 'John'},
        {name: 'Bill'},
      ]
    }
    my-component.html
    <li repeat.for="item of items">${item.name}</li>
    <p repeat.for="i of 10">${10-i}</p>
    <p>Blast Off!<p>
    repeater-template.ts
    export class RepeaterTemplate {
        friends: Set<string> = new Set();
        
        constructor() {
          this.friends.add('Alice');
          this.friends.add('Bob');
          this.friends.add('Carol');
          this.friends.add('Dana');
        }
    }
    repeater-template.html
    <template>
         <p repeat.for="friend of friends">Hello, ${friend}!</p>
    </template>
    repeater-template.ts
    export class RepeaterTemplate {
        friends = new Map();
      
        constructor() {
            this.friends.set('Hello', { name: 'Alice' });
        
            this.friends.set('Hola', { name: 'Bob' });
        
            this.friends.set('Ni Hao', { name: 'Carol' });
        
            this.friends.set('Molo', { name: 'Dana' });
      }
    }
    repeater-template.html
    <p repeat.for="[greeting, friend] of friends">${greeting}, ${friend.name}!</p>
    repeater-template.html
    <p repeat.for="greeting of friends | keys">${greeting}, ${friends[greeting].name}!</p>
    repeater-template.ts
    export class RepeaterTemplate {
      constructor() {
        this.friends = {
          'Hello':
            { name : 'Alice' },
          'Hola':
            { name : 'Bob' },
          'Ni Hao':
            { name : 'Carol' },
          'Molo':
            { name : 'Dana' }
        }
      }
    }
    // resources/value-converters/keys.ts
    export class KeysValueConverter {
      toView(obj): string[] {
        return Reflect.ownKeys(obj);
      }
    }
    <import from="resources/value-converters/keys"></import>
    
    <p repeat.for="greeting of friends | keys">${greeting}, ${friends[greeting].name}!</p>

    Transition plan

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

    How does it work

    The child routes inherits the transitionPlan from the parent.

    When the transitionPlan property in the is not configured, router-lite uses a function as the sensible default to select the transition plan when the current component is attempted to be loaded again. The default behavior selects replace when the parameter changes 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 are inherited

    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.

    Use a function to dynamically select transition plan

    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 clicking the link 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 , 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 click the link 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.

    DOM style injection

    Some users may want to inject their styles into the DOM like v1. There is no equivalent for this functionality in the second version so we can define our injectStyle function as following:

    toCss(obj)

    A very simple object-to-css converter.

    Parameter(s)
    Description
    Optional
    Default

    Returns: a css text.

    The value of cssText will be equal to:

    You can use to convert a CSS text to a JS object and use the result for toCss(result) directly!

    There is a conventional naming approach for defining your rules. Every uppercase character will change to a hyphen and a lowercase character (XyZ => -xy-z). For example, If you want to achieve -webkit-animation you should write WebkitAnimation or flexDirection will change to flex-direction.

    This is exactly what the caseConverter function does.

    injectStyle(textOrObject, id, overridable, hostElement)

    A functionality to inject your text or object-based style to the html document easily!

    Parameter(s)
    Description
    Optional
    Default
    router-lite - events - StackBlitzStackBlitz

    Styling components

    Styling components using CSS, CSS pre and post-processors as well as working with web components.

    Aurelia makes it easy to work with component styles. From the type of CSS flavor you prefer to work with (CSS, PostCSS, SASS, Stylus) to how you use those styles in your components. At the end of the day, regardless of what CSS flavor you choose, it all compiles into CSS.

    Style conventions

    This section will teach you how to style components using conventions and Shadow DOM to encapsulate your CSS styles.

    Not always, but sometimes when you are creating components, you also want styles for your component. Default conventions for custom elements will be imported automatically if you create a custom element and a CSS file with a matching filename.

    Binding behaviors

    Binding behaviors are a category of view resource, just like value converters, custom attributes and custom elements. Binding behaviors are most like in that you use them declaratively in binding expressions to affect the binding.

    The primary difference between a binding behavior and a value converter is binding behaviors have full access to the binding instance, throughout it's lifecycle. Contrast this with a value converter which only has the ability to intercept values passing from the model to the view and visa versa.

    The additional "access" afforded to binding behaviors gives them the ability to change the behavior of the binding, enabling a lot of interesting scenarios which you'll see below.

    Throttle

    Web Components

    The basics of the web-component plugin for Aurelia.

    Introduction

    Web Components are part of an ever-evolving web specification that aims to allow developers to create native self-contained components without the need for additional libraries or transpilation steps. This guide will teach you how to use Aurelia in Web Components.

    Dependency injection (DI)

    Dependency injection is a powerful tool that enables Aurelia and its component model and can assist in building SOLID modular architectures in any object-oriented environment. In Aurelia, dependency injection underpins much of what you do in the framework when working with components and resources.

    To learn about dependency injection and why you should use it, consult our section.

    Declaring injectable dependencies

    router-lite - navigation-model - StackBlitzStackBlitz
    router-lite - navigation-model - isactive - StackBlitzStackBlitz
    Aurelia Reactive Animation - StackBlitzStackBlitz
    Aurelia Router Animations - StackBlitzStackBlitz
    // inject-style.ts
    
    function caseConverter(str: string): string {
      if (str.length === 0) return str;
      const isUppercase = str[0] === str[0].toUpperCase();
      const result = str
        .split(/(?=[A-Z])/)
        .join("-")
        .toLowerCase();
      return isUppercase ? `-${result}` : result;
    }
    
    // CSS object to string converter.
    export function toCss(obj: any): string {
      if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
        throw new TypeError(
          `expected an argument of type object, but got ${typeof obj}`
        );
      }
      let lines: string[] = [];
      for (let index = 0; index < Object.keys(obj).length; index++) {
        const id = Object.keys(obj)[index];
        const key = caseConverter(id);
        const value = obj[id];
        if (typeof value === "object") {
          const text = toCss(value);
          lines.push(`${id}{${text}}`);
        } else {
          lines.push(`${key}:${value};`);
        }
      }
      return lines.join("");
    }
    
    // Inject string/object style to DOM.
    export function injectStyle(
      textOrObject: string | object,
      id?: string,
      overridable = true,
      hostElement: HTMLElement = document.head
    ) {
      if (!textOrObject || Array.isArray(textOrObject)) return;
      if (typeof textOrObject === 'string' && textOrObject.length === 0) return;
      if (id) {
        let oldStyle = document.getElementById(id);
        if (oldStyle) {
          let isStyleTag = oldStyle.tagName.toLowerCase() === "style";
          if (!isStyleTag) {
            throw new Error("The provided id does not indicate a style tag.");
          } else if (isStyleTag && overridable) {
            oldStyle.innerHTML = typeof textOrObject === "object" ? toCss(textOrObject) : textOrObject;
          }
          return;
        }
      }
      let style = document.createElement("style");
      style.type = "text/css";
      style.innerHTML = typeof textOrObject === "object" ? toCss(textOrObject) : textOrObject;
      if (id) style.id = id;
      hostElement.appendChild(style);
    }
    Logo
    Logo

    Yes

    true

    hostElement

    To set your host element to inject your style into it. Useful for shadow DOM.

    Yes

    document.head

    obj

    Object-based style

    No

    -

    textOrObject

    Text or object-based style.

    No

    -

    id

    To set an id for your <style>, it helps you to update an specific style tag.

    Yes

    -

    overridable

    css-to-js transformer

    If set this to false, your style is only injected once but to do this you must set an id parameter too.

    export class MyComponent {
    
    }
    .myclass {
      color: red;
    }
    <p class="myclass">This is red!</p>

    Notice how we didn't import my-component.css But when we run the app, it gets imported automatically? That is Aurelia's intuitive conventions at work.

    Shadow DOM

    One of the most exciting web specifications is Web Components. Part of the Web Components specification is Shadow DOM API. Shadow DOM allows us to encapsulate HTML and styles within our web applications, allowing us to deal with a problem that has plagued web development since the beginning: global styles.

    In Aurelia, you can work with Shadow DOM in three different ways:

    1. Global Shadow DOM — all components in your application will be encapsulated

    2. Configured Shadow DOM — allows you to opt-in to using Shadow DOM on some components using the decorator useShadowDOM

    3. Global opt-out Shadow DOM is a mix of the previous two configuration options. You can turn on Shadow DOM globally but then disable it on a per-component basis.

    If you use CSS libraries such as Bootstrap, which rely on global styles, you must account for this when using Shadow DOM. See below in the "Global shared styles" section for more information.

    Setting up Shadow DOM

    If you didn't choose Shadow DOM during the initial CLI setup phase using Makes, you can manually configure Aurelia to use Shadow DOM inside of main.ts.

    Importing the StyleConfiguration class allows us to configure Aurelia to work with different style modes, including Shadow DOM.

    If you are using Webpack, additional configuration work needs to be done. The Aurelia CLI output of makes will add the following rule into your Webpack configuration.

    Global shared styles

    Unlike the traditional way CSS works (populating the global scope), the concept of global styles in Shadow DOM does not exist. However, you can configure shared styles if you require reusable global-like styles.

    A great example of shared styles is using the Bootstrap CSS library.

    You can use the sharedStyles configuration property to share any CSS styles. Furthermore, the sharedStyles property accepts an array of shared styles, so you can have more than one shared stylesheet that all components will get access to.

    Component configuration using useShadowDOM

    As we mentioned earlier, Aurelia provides a decorator that allows you to opt in and opt out of using Shadow DOM in your web applications. This decorator is called useShadowDOM and can be imported from the main aurelia package.

    Using the decorator without providing any configuration options will enable Shadow DOM on your component in the default mode of open.

    The useshadowDOM decorator allows you to specify the mode as a configuration property. For reference on the available modes open and closed please see below for more details. You can also consult MDN docs here.

    Or, to explicitly specify your component as open:

    Understanding Shadow DOM modes

    The Shadow DOM API provides two modes you can use for your components. These modes are part of the specification itself and not Aurelia-specific.

    open

    Open mode is the default setting for Shadow DOM. This allows you to access the DOM of a component using Javascript. The DOM is accessible via the shadowRoot property accessible on the element.

    closed

    The closed mode does the exact opposite of open in that the shadowRoot property of your component will return null as the component is closed. This is useful for instances where you want components to only be accessible from within.

    Where possible, it is recommended that you use open mode for your Shadow DOM-based components. Open mode allows you to use e2e testing and libraries that rely on the DOM being accessible.

    Disabling Shadow DOM

    The useShadowDom decorator allows you to change the mode but can also accept a false value to disable Shadow DOM on a specific component.

    Shadow DOM special selectors

    When working with Shadow DOM component styles, you have access to various special selectors, allowing you to style components in particular ways. These selectors can be found in the CSS Scoping Module level 1 specification.

    :host

    Every custom element in Aurelia has an HTML tag name. If you have a custom element called app-header your HTML tags, this leads us to the use of the :host selector. The host element is the HTML element itself.

    This would add a border to the app-header element but not the child elements. Using the function form :host() allows you to target elements inside of the custom element.

    :host-context

    The host-context selector allows you to target your elements based on the outer context. A valid use for this type of selector is to implement theme functionality allowing your components to be styled. This would be especially helpful if you have a light or dark mode feature.

    CSS Modules

    In some instances, Shadow DOM is not the right fit for your web application needs, particularly the constraints around global styles. If you need an in-between solution that allows you to encapsulate and use global styles, CSS Modules might be a desired option.

    To use CSS Modules, the process is a lot more straightforward if you use the npx makes aurelia CLI tool and choose CSS Modules as your preferred styling option. Otherwise, you will have to configure your application manually.

    Please note the following loader configuration only applies to Webpack.

    You need to do nothing special to use CSS Modules in your components. Specify your class names and then reference them in your HTML. The bundler turns that class value into a randomly generated string value.

    In your HTML, you might reference this class like this:

    And once the CSS Modules functionality rewrites the class, it might end up looking like this:

    CSS Modules' special selectors

    Much like the Shadow DOM styling option, you can use some special selectors when using CSS Modules.

    :global

    The global selector allows you to style global CSS selectors without transforming them. For some CSS classes, you want them to be global for active states. In fact, the default Aurelia app generated using Makes comes with an example of :global being used.

    This selector especially comes in use when working with CSS libraries like Bootstrap, allowing you to target global CSS selectors.

    Additional Reading

    You should consult the CSS classes and styling section to learn how to work with classes and styles in your Aurelia applications, where strategies for working with classes as well as inline CSS styles are discussed.

    Aurelia ships with a handful of behaviors out of the box to enable common scenarios. The first is the throttle binding behavior which limits the rate at which the view-model is updated in two-way bindings or the rate at which the view is updated in to-view binding scenarios.

    By default throttle will only allow updates every 200ms. You can customize the rate of course. Here are a few examples.

    Updating a property, at most, every 200ms

    HTML

    The first thing you probably noticed in the example above is the & symbol, which is used to declare binding behavior expressions. Binding behavior expressions use the same syntax pattern as value converter expressions:

    • Binding behaviors can accept arguments: firstName & myBehavior:arg1:arg2:arg3

    • A binding expression can contain multiple binding behaviors: firstName & behavior1 & behavior2:arg1.

    • Binding expressions can also include a combination of value converters and binding behaviors: ${foo | upperCase | truncate:3 & throttle & anotherBehavior:arg1:arg2}.

    Here's another example using throttle, demonstrating the ability to pass arguments to the binding behavior:

    Updating a property, at most, every 850ms

    The throttle behavior is particularly useful when binding events to methods on your view-model. Here's an example with the mousemove event:

    Handling an event, at most, every 200ms

    Debounce

    The debounce binding behavior is another rate-limiting binding behavior. Debounce prevents the binding from being updated until a specified interval has passed without any changes.

    A common use case is a search input that triggers searching automatically. You wouldn't want to make a search API on every change (every keystroke). It's more efficient to wait until the user has paused typing to invoke the search logic.

    Update after typing stopped for 200ms

    Update after typing stopped for 850ms

    Like throttle, the debounce binding behavior shines in event binding.

    Here's another example with the mousemove event:

    Call mouseMove after mouse stopped moving for 500ms

    UpdateTrigger

    Update trigger allows you to override the input events that cause the element's value to be written to the view-model. The default events are change and input.

    Here's how you would tell the binding to only update the model on blur:

    Update on blur

    Multiple events are supported:

    Update with multiple events

    Signal

    The signal binding behavior enables you to "signal" the binding to refresh. This is especially useful when a binding result is impacted by global changes outside **** the observation path.

    For example, if you have a "translate" value converter that converts a key to a localized string- eg ${'greeting-key' | translate} and your site allows users to change the current language, how would you refresh the bindings when that happens?

    Another example is a value converter that uses the current time to convert a record's datetime to a "time ago" value: posted ${postDateTime | timeAgo}. The moment this binding expression is evaluated it will correctly result in posted a minute ago. As time passes, it will eventually become inaccurate. How can we refresh this binding periodically so that it correctly displays 5 minutes ago, then 15 minutes ago, an hour ago, etc?

    Here's how you would accomplish this using the signal binding behavior:

    Using a Signal

    In the binding expression above, we're using the signal binding behavior to assign the binding a "signal name" of my-signal. Signal names are arbitrary. You can give multiple bindings the same signal name if you want to signal multiple bindings simultaneously.

    Here's how we can use the ISignaler to signal the bindings periodically:

    Signaling Bindings

    oneTime

    With the oneTime binding behavior you can specify that string interpolated bindings should happen once. Simply write:

    One-time String Interpolation

    This is an important feature to expose. One-time bindings are the most efficient type of binding because they don't incur any property observation overhead.

    There are also binding behaviors for toView and twoWay which you could use like this:

    To-view and two-way binding behaviours

    The casing for binding modes is different depending on whether they appear as a binding command or as a binding behavior. Because HTML is case-insensitive, binding commands cannot use capitals. Thus, the binding modes, when specified in this place, use lowercase, dashed names. However, when used within a binding expression as a binding behavior, they must not use a dash because that is not a valid symbol for variable names in JavaScript. So, in this case, camel casing is used.

    Self

    With the self binding behavior, you can specify that the event handler will only respond to the target to which the listener was attached, not its descendants.

    For example, in the following markup

    Self-binding behavior

    onMouseDown is your event handler, and it will be called not only when user mousedown on header element, but also all elements inside it, which in this case are the buttons settings and close. However, this is not always desired behavior. Sometimes you want the component only to react when user clicks on the header itself, not the buttons. To achieve this, onMouseDown method needs some modification:

    Handler without self-binding behavior

    This works, but now business/ component logic is mixed up with DOM event handling, which is not necessary. Using self binding behavior can help you achieve the same goal without filling up your methods with unnecessary code:

    Using self-binding behavior

    Using self-binding behavior

    Custom binding behaviors

    You can build custom binding behaviors just like you can build value converters. Instead of toView and fromView methods you'll create bind(binding, scope, [...args]) and unbind(binding, scope) methods. In the bind method you'll add your behavior to the binding and in the unbind method you should clean up whatever you did in the bind method to restore the binding instance to it's original state. The binding argument is the binding instance whose behavior you want to change. It's an implementation of the Binding interface. The scope argument is the binding's data context. It provides access to the model the binding will be bound to via it's bindingContext and overrideContext properties.

    Here's a custom binding behavior that calls a method on your view model each time the binding's updateSource / updateTarget and callSource methods are invoked.

    value converters
    Installing The Plugin

    To use the plugin, import the interface IWcElementRegistry interface from @aurelia/runtime-html module and start defining web-component custom elements by calling method define on the instance of IWcElementRegistry.

    WC custom elements can be defined anytime, either at the application start or later. Applications are responsible for ensuring names are unique.

    Extending built-in elements is supported via the 3rd parameter of the define call, like the define call on the global window.customElements.define call.

    How it works

    • Each of WC custom element will be backed by a view model, like a normal Aurelia element component.

    • For each define call, a corresponding native custom element class will be created and defined.

    • Each bindable property on the backing Aurelia view model will be converted to a reactive attribute (via observedAttributes) and reactive property (on the prototype of the extended HTML Element class created).

    • Slot: [au-slot] is not supported when upgrading an existing element. slot can be used as a normal WC custom element.

    Notes:

    • WC custom element works independently with the Aurelia component. This means the same class can be both a WC custom element and an Aurelia component. Though this should be avoided as it could result in double rendering.

    • containerless mode is not supported. Use extend-built-in instead if you want to avoid wrappers.

    • the defined WC custom elements will continue working even after the owning Aurelia application has stopped.

    • template info will be retrieved & compiled only once per define call. Changing it after this call won't have any effects.

    • bindables info will be retrieved & compiled only once per define call. Changing it after this call won't have any effects.

    Examples

    For simplicity, all the examples below define elements at the start of an application, but they can be defined at any time.

    1. Defining a tick-clock element

    1. Defining a tick-clock element using shadow DOM with open mode

    1. Injecting the host element into the view model

    1. Defining a tick-clock element with format bindable property for formatting

    1. Defining a tick-clock element extending built-in div element:

    // css-object-style.ts
    
    export const cssObj = {
      ".main-wrapper": { flexDirection: "row", display: "flex", flex: "1" },
      "#content": { flex: "1" },
      ul: { padding: "20px 0", flex: "1" },
      li: { fontFamily: "'Lato'", color: "whitesmoke", lineHeight: "44px" }
    };
    
    let cssText = toCss(cssObj);
    .main-wrapper {
      flex-direction: row;
      display: flex;
      flex: 1;
    }
    #content {
      flex: 1;
    }
    ul {
      padding: 20px 0;
      flex: 1;
    }
    li {
      font-family:'Lato';
      color: whitesmoke;
      line-height: 44px;
    }
    import { injectStyle } from 'inject-style';
    import { cssObj } from 'css-object-style';
    
    // Object
    injectStyle(cssObj, 'my-style-tag', false);
    
    // String
    injectStyle(`
    .main-wrapper {
      flex-direction: row;
      display: flex;
      flex: 1;
    }
    #content {
      flex: 1;
    }
    ul {
      padding: 20px 0;
      flex: 1;
    }
    li {
      font-family:'Lato';
      color: whitesmoke;
      line-height: 44px;
    }
    `, 'my-style-tag', false);
    import Aurelia, { StyleConfiguration } from 'aurelia';
    import { MyApp } from './my-app';
    
    Aurelia
      .register(StyleConfiguration.shadowDOM({}))
      .app(MyApp)
      .start();
    {
      test: /[/\\]src[/\\].+\.html$/i,
      use: {
        loader: '@aurelia/webpack-loader',
        options: {
          defaultShadowOptions: { mode: 'open' }
        }
      },
      exclude: /node_modules/
    }
    import Aurelia, { StyleConfiguration } from 'aurelia';
    import { MyApp } from './my-app';
    
    import bootstrap from 'bootstrap/dist/css/bootstrap.css';
    
    Aurelia
      .register(StyleConfiguration.shadowDOM({
        // optionally add the shared styles for all components
        sharedStyles: [bootstrap]
      }))
      .app(MyApp)
      .start();
    import { useShadowDOM } from 'aurelia';
    
    @useShadowDOM()
    export class MyComponent {
    
    }
    import { useShadowDOM } from 'aurelia';
    
    @useShadowDOM({mode: 'closed'})
    export class MyComponent {
    
    }
    import { useShadowDOM } from 'aurelia';
    
    @useShadowDOM({mode: 'open'})
    export class MyComponent {
    
    }
    import { useShadowDOM } from 'aurelia';
    
    @useShadowDOM(false)
    export class MyComponent {
    
    }
    app-header.css
    :host {
        border: 1px solid #000;
    }
    app-header.css
    :host {
        border: 1px solid #000;
    }
    
    :host(.active) {
        font-weight: bold;
    }
    :host-context(.dark-mode) {
        border: 1px solid white;
    }
    webpack.config.js
    {
      test: /[/\\]src[/\\].+\.html$/i,
      use: {
        loader: '@aurelia/webpack-loader',
        options: { useCSSModule: true }
      },
      exclude: /node_modules/
    }
    .madeup-style {
        font-style: italic;
    }
    <a class="madeup-style">Italic link</a>
    <a class="nNe4iytdPCf3HfNJUXiz">Italic link</a>
    :global(.load-active) {
        background-color: lightgray;
    }
    <input type="text" value.bind="query & throttle">
    <input type="text" value.bind="query & throttle:850">
    <div mousemove.delegate="mouseMove($event) & throttle"></div>
    <input type="text" value.bind="query & debounce">
    <input type="text" value.bind="query & debounce:850">
    <div mousemove.delegate="mouseMove($event) & debounce:500"></div>
    <input value.bind="firstName & updateTrigger:'blur'>  
    <input value.bind="firstName & updateTrigger:'blur':'paste'>
    posted ${postDateTime | timeAgo & signal:'my-signal'}
    import { ISignaler } from 'aurelia';
      
    export class MyApp {
      constructor(@ISignaler readonly signaler: ISignaler) {
        setInterval(() => signaler.signal('my-signal'), 5000);
      }
    }
    <span>${foo & oneTime}</span>
    <input value.bind="foo & toView">
    <input value.to-view="foo">
      
    <input value.bind="foo & twoWay">
    <input value.two-way="foo">  
    <panel>
      <header mousedown.delegate='onMouseDown($event)' ref='header'>
        <button>Settings</button>
        <button>Close</button>
      </header>
    </panel>
    // inside component's view model class
    onMouseDown(event) {
      // if mousedown on the header's descendants. Do nothing
      if (event.target !== header) return;
      // mousedown on header, start listening for mousemove to drag the panel
      // ...
    }
    <panel>
      <header mousedown.delegate='onMouseDown($event) & self'>
        <button class='settings'></button>
        <button class='close'></button>
      </header>
    </panel>
    // inside component's view model class
    onMouseDown(event) {
      // No need to perform check, as the binding behavior will ensure check
      // if (event.target !== header) return;
      // mousedown on header, start listening for mousemove to drag the panel
      // ...
    }
      const interceptMethods = ['updateTarget', 'updateSource', 'callSource'];
      export class InterceptBindingBehavior {
        bind(scope, binding) {
          let i = interceptMethods.length;
          while (i--) {
            let methodName = interceptMethods[i];
            let method = binding[method];
            if (!method) {
              continue;
            }
            binding[`intercepted-${methodName}`] = method;
            binding[methodName] = method.bind(binding);
          }
        }
      
        unbind(scope, binding) {
          let i = interceptMethods.length;
          while (i--) {
            let methodName = interceptMethods[i];
            if (!binding[methodName]) {
              continue;
            }
            binding[methodName] = binding[`intercepted-${methodName}`];
            binding[`intercepted-${methodName}`] = null;
          }
        }
      }
      
    <import from="./intercept-binding-behavior"></import>
    
    <div mousemove.delegate="mouseMove($event) & intercept:myFunc"></div>
    
    <input value.bind="foo & intercept:myFunc">
    import { Aurelia, IWcElementRegistry } from 'aurelia';
    
    Aurelia
      .register(
        AppTask.creating(IWcElementRegistry, registry => {
          registry.define('tick-clock', class TickClock {
            static template = '${message}';
    
            constructor() {
              this.time = Date.now();
            }
    
            attaching() {
              this.intervalId = setInterval(() => {
                this.message = `${Date.now() - this.time} seconds passed.`;
              }, 1000)
            }
    
            detaching() {
              clearInterval(this.intervalId);
            }
          })
        })
      )
      .app(class App {})
      .start();
    import { Aurelia, IWcElementRegistry } from 'aurelia';
    
    Aurelia
      .register(
        AppTask.creating(IWcElementRegistry, registry => {
          registry.define('tick-clock', class TickClock {
            static template = '${message}';
            static shadowOptions = { mode: 'open' };
    
            constructor() {
              this.time = Date.now();
            }
    
            attaching() {
              this.intervalId = setInterval(() => {
                this.message = `${Date.now() - this.time} seconds passed.`;
              }, 1000)
            }
    
            detaching() {
              clearInterval(this.intervalId);
            }
          })
        })
      )
      .app(class App {})
      .start();
    import { INode, Aurelia, IWcElementRegistry } from 'aurelia';
    
    Aurelia
      .register(
        AppTask.creating(IWcElementRegistry, registry => {
          registry.define('tick-clock', class TickClock {
            static template = '${message}';
            static shadowOptions = { mode: 'open' };
    
            // all these injections result in the same instance
            // listing them all here so that applications can use what they prefer
            // based on HTMLElement 
            static inject = [INode, Element, HTMLElement];
    
            constructor(node, element, htmlElement) {
              node === element;
              element === htmlElement;
              this.time = Date.now();
            }
    
            attaching() {
              this.intervalId = setInterval(() => {
                this.message = `${Date.now() - this.time} seconds passed.`;
              }, 1000)
            }
    
            detaching() {
              clearInterval(this.intervalId);
            }
          })
        })
      )
      .app(class App {})
      .start();
    import { INode, Aurelia, IWcElementRegistry } from 'aurelia';
    
    document.body.innerHTML = '<tick-clock format="short"></tick-clock>';
    
    Aurelia
      .register(
        AppTask.creating(IWcElementRegistry, registry => {
          registry.define('tick-clock', class TickClock {
            static template = '${message}';
            static shadowOptions = { mode: 'open' };
            static bindables = ['format'];
    
            // all these injections result in the same instance
            // listing them all here so that applications can use what they prefer
            // based on HTMLElement 
            static inject = [INode, Element, HTMLElement];
    
            constructor(node, element, htmlElement) {
              node === element;
              element === htmlElement;
              this.time = Date.now();
            }
    
            attaching() {
              this.intervalId = setInterval(() => {
                this.message = `${(Date.now() - this.time)/1000} ${this.format === 'short' ? 's' : 'seconds'} passed.`;
              }, 1000)
            }
    
            detaching() {
              clearInterval(this.intervalId);
            }
          })
        })
      )
      .app(class App {})
      .start();
    import { Aurelia, IWcElementRegistry } from 'aurelia';
    
    document.body.innerHTML = '<div is="tick-clock"></div>'
    
    Aurelia
      .register(
        AppTask.creating(IWcElementRegistry, registry => {
          registry.define('tick-clock', class TickClock {
            static template = '${message}';
    
            constructor() {
              this.time = Date.now();
            }
    
            attaching() {
              this.intervalId = setInterval(() => {
                this.message = `${Date.now() - this.time} seconds passed.`;
              }, 1000)
            }
    
            detaching() {
              clearInterval(this.intervalId);
            }
          })
        }, { extends: 'div' })
      )
      .app(class App {})
      .start();
    Injecting into plain classes

    To declare a dependency on a plain class, you can use one of three techniques, depending on what JavaScript/TypeScript features you want to use.

    Use static property

    You can declare your desired injected dependencies by adding a inject static property to your class. The inject property should reference an array of items to be injected, where each item in the array corresponds to a constructor argument.

    In this case, we have a class designed to import files. The importer uses a file system abstraction (FileReader) and a logging abstraction (Logger) to do its job.

    The importer declares these in its inject array and then creates a constructor with the matching inputs. When the DI container instantiates the importer, it will locate or instantiate its dependencies and supply them to the constructor at runtime.

    The order is important when using the static injection method. The order of dependencies in the array is how they will be passed through to the constructor.

    Use a decorator

    Decorators are an upcoming feature of EcmaScript and an experimental feature in TypeScript. If you want to use decorators in your project, you must first opt into them by adding "experimentalDecorators": true in the compilerOptions section of your tsconfig.json file. Once enabled, you can import the @inject decorator from this library and use it to declare injected dependencies. Here's the same example from above, written with a decorator:

    This decorator creates a static inject property on the decorated class, with an array whose items correspond to the decorator's arguments. It's just a bit of syntax sugar over the base implementation. You could even write an app-specific decorator to accomplish the same result if desired.

    Use compiler metadata

    If you have already opted into using TypeScript and decorators, you have an additional option: decorator metadata. The TypeScript compiler can emit metadata about the types of the constructor parameters automatically as part of the build process if a decorator is present. If this metadata is present, the DI container can use it instead of an explicit injection list.

    To enable this feature for the compiler, add "emitDecoratorMetadata": true in the compilerOptions section of your tsconfig.json file. With that in place, you can write the above code like this:

    When the inject decorator specifies no arguments; the DI container will attempt to look up the metadata that the TypeScript compiler has associated with the importer class. This approach is nice since it allows the constructor of the class to be the single source of truth for the information on what to inject.

    The secret of TypeScript's metadata generation is that any decorator on the class will cause the compiler to include the metadata. You don't have to use the inject decorator specifically.

    Besides the experimental nature of this feature, there are some drawbacks to be aware of:

    • You can only use classes for the types of your constructor parameters. Interfaces don't exist at runtime, so using them, in this case, will result in missing metadata for the type at runtime. Using a symbol or other value won't work either. This constraint tends to be fairly limited in practice.

    • You cannot use custom resolvers. See the section below to learn about resolvers like all and lazy. Since these provide additional information to the DI about resolving the dependency, they cannot stand in as a type for the TypeScript compiler to store in metadata.

    If you have either of the above scenarios, you'll want to use an explicit dependency list through the decorator or the static property.

    Creating containers

    An application typically has a single root-level DI container. To create a root container, call the DI.createContainer() method:

    Once you have a root-level container, you'll typically configure it with any services and then resolve your composition root, allowing you to kick off instantiation.

    Registering services

    The DI container uses a technique called auto-registration. This means that if a class requests another class to be injected, and the requested class is not already registered with the container, the container will automatically register the class as a singleton, create the instance, and return it to the requestor.

    Auto-registration works if everything is a singleton and you're using classes for dependencies everywhere and not using any interfaces or objects directly. That's very rarely the case, though. Additionally, you may want to declare the behavior of your services upfront, to make the code more understandable for other engineers. To do this, the DI container provides a registration API.

    The primary API for registration is called register , and it can be used in combination with the Registration DSL. Here's an example:

    With each registration, we provide a key and a value. The key is what consumers will use to request the dependency. The value is what the DI container will provide. The type of value you configure here depends on the registration type.

    Below is a list of available options, along with explanations:

    • Registration.instance(key: any, value: any): IRegistration - Creates a registration for an existing object instance with the container so that the specified key can look it up.

    • Registration.singleton(key: any, value: Function): IRegistration - Creates a registration for a singleton. The value is a class that will be instantiated when first requested. The DI container will retain a reference to the created instance and will return the same instance to all subsequent requestors.

    • Registration.transient(key: any, value: Function): IRegistration - Creates a registration for a transient instance. The value is a class that will be instantiated whenever requested. The container does not retain a reference to the created instance.

    • Registration.callback(key: any, value: ResolveCallback): IRegistration - Creates a registration that enables custom instantiation and lifetime behavior. ResolveCallback is defined as follows:

      Provide a function with the above signature, and the container will invoke you each time it needs to resolve an instance. Note that the resolver that is provided as the third parameter is the resolver associated with your factory. As such, should your resolver need to store the state used across requests, it can store that state on the resolver instance itself.

    • Registration.alias(originalKey: any, aliasKey: any): IRegistration - Creates a registration that allows access to a previous registration via an additional name. The originalKey is the key used in the original registration. The aliasKey is the 2nd (or 3rd, 4th, etc.) key that you also want to be able to resolve the same behavior.

    You will notice that each of these helper methods returns an IRegistration implementation. The register method of the container can handle anything that implements this interface, so you can create your own registration implementations. Here's what that interface looks like:

    Note: For a deeper exploration of how to implement custom registrations and resolvers, see the internal registration/resolver implementation that handles all the registration scenarios listed above.

    Resolving services

    In most cases, the resolution will happen automatically through constructor injection. However, you'll typically need to manually resolve the root service to kick the whole thing off. Other scenarios may come up that require manual resolution. To resolve from a container, call the get API on the container.

    Here's an example:

    If there are multiple different implementations for the same key, then you can resolve all of them with the getAll API:

    Using interfaces

    When using DI on other platforms, one would typically register services with an interface. However, TypeScript interfaces only exist at compile-time, not runtime, and the JavaScript language doesn't (currently) support interfaces. As a result, using an interface for the registration key cannot work. However, a feature of the TypeScript language lets you get around this limitation. Because TypeScript can merge definitions, it's possible to create an interface and a symbol with the same name. Here's an example:

    When this technique is used, TypeScript, based on usage, can determine in which scenarios you want to use the interface and in which you want to use the Symbol. So, it can completely understand this code:

    Warning You cannot use decorator metadata in this case because the TypeScript compiler cannot understand that it should encode the variable value into the metadata in place of the interface (which gets erased).

    Because this is such a handy compiler capability, the DI API provides a helper method with some benefits. We can optionally rework the above code to use this API like so:

    This is slightly more verbose, but it has the advantage that the symbol created by the DI.createInterface() API "captures" the generic parameter, allowing it to work seamlessly with the get APIs of the container providing strong return types when manually resolving with these symbols.

    Default interface implementations

    In many front-end scenarios, it's common to have a single default implementation of your interface, which you provide "out of the box". To facilitate this scenario, the DI.createInterface() method accepts a callback that you can use to associate a default implementation with your interface.

    The consumer who sets up the container can still specify their own interface implementation at runtime. Still, if one is not provided, it will fall back to the default implementation specified through this method. This allows the container to use auto-registration with the symbols created by the DI.createInterface() helper.

    The callback that creates the default registration. This API looks nearly identical to the Registration DSL described above.

    what is Dependency Injection
    routing configuration
    sibling viewports
    router-lite - navigation-model - exclusion - StackBlitzStackBlitz
    Logo
    Logo
    Aurelia Wiggle Animation - StackBlitzStackBlitz
    Logo
    Aurelia Stateful Animation - StackBlitzStackBlitz

    Component basics

    Components underpin most of what you do in Aurelia. Learn all there is to know about authoring components, working with dependencies and more.

    Custom elements underpin Aurelia applications. They are what you will be spending most of your time creating and can be consisted of the following:

    • An HTML template (view)

    • A class that constitutes the view model

    • An optional CSS stylesheet

    Getting Started

    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 section at the end of this guide.

    Router Tutorial

    Introduction

    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.

    router-lite - getting started - StackBlitzStackBlitz
    export class FileImporter {
      public static readonly inject = [FileReader, Logger];
      
      constructor(private fileReader: FileReader, logger: Logger) { ... }
    }
    import { inject } from 'aurelia';
    
    @inject(FileReader, Logger)
    export class FileImporter {
      constructor(private fileReader: FileReader, logger: Logger) { ... }
    }
    import { inject } from 'aurelia';
    
    @inject()
    export class FileImporter {
      constructor(private fileReader: FileReader, logger: Logger) { ... }
    }
    import { DI } from 'aurelia';
    
    const container = DI.createContainer();
    import { DI, Registration } from 'aurelia';
    
    const container = DI.createContainer();
    container.register(
      Registration.singleton(ProfileService, ProfileService),
      Registration.instance(fetch, fakeFetch)
    );
    export interface IRegistration<T = any> {
      register(container: IContainer, key?: any): IResolver<T>;
    }
    const profileService: ProfileService = container.get(ProfileService);
    const panels: Panel[] = container.getAll(Panel);
    export const IProfileService = Symbol('IProfileService');
    export interface IProfileService { ... }
    import { Registration } from 'aurelia';
    
    const IProfileService = Symbol('IProfileService');
    interface IProfileService { ... }
    
    class ProfileService implements IProfileService {
        // ...implementation...
    }
    
    class SomeClass {
      inject = [IProfileService];
      constructor(service: IProfileService) {
            // ...implementation...
        }
    }
    
    container.register(
      Registration.singleton(IProfileService, ProfileService)
    );
    import { DI } from 'aurelia';
    
    export const IProfileService = DI.createInterface<IProfileService>();
    export interface IProfileService {
        // ...implementation...
    }
    import { DI } from 'aurelia';
    
    export interface ITaskQueue {
        // ...api...
    }
    
    export const ITaskQueue = DI.createInterface<ITaskQueue>(x => x.singleton(TaskQueue));
    
    class TaskQueue implements ITaskQueue {
        // ...implementation...
    }
    import { customElement } from '@aurelia/runtime-html';
    import { IRouteViewModel, route } from '@aurelia/router-lite';
    
    @customElement({ name: 'ce-one', template: 'ce1 ${id1} ${id2}' })
    class CeOne implements IRouteViewModel {
      private static id1: number = 0;
      private static id2: number = 0;
      // Every instance gets a new id.
      private readonly id1: number = ++CeOne.id1;
      private id2: number;
      public canLoad(): boolean {
        // Every time the lifecycle hook is called, a new id is generated.
        this.id2 = ++CeOne.id2;
        return true;
      }
    }
    
    @route({
      transitionPlan: 'replace',
      routes: [
        {
          id: 'ce1',
          path: ['', 'ce1'],
          component: CeOne,
        },
      ],
    })
    @customElement({
      name: 'my-app',
      template: `<a load="ce1">ce-one</a><br><au-viewport></au-viewport>`,
    })
    export class MyApp {}
    import { customElement } from '@aurelia/runtime-html';
    import { IRouteViewModel, route } from '@aurelia/router-lite';
    
    @customElement({ name: 'ce-one', template: 'ce1 ${id1} ${id2}' })
    class CeOne implements IRouteViewModel {
      private static id1: number = 0;
      private static id2: number = 0;
      // Every instance gets a new id.
      private readonly id1: number = ++CeOne.id1;
      private id2: number;
      public canLoad(): boolean {
        // Every time the lifecycle hook is called, a new id is generated.
        this.id2 = ++CeOne.id2;
        return true;
      }
    }
    
    @route({
      transitionPlan(_current: RouteNode, next: RouteNode) {
        return next.component.Type === MyApp ? 'replace' : 'invoke-lifecycles';
      },
      routes: [
        {
          id: 'ce1',
          path: ['', 'ce1'],
          component: CeOne,
        },
      ],
    })
    @customElement({
      name: 'my-app',
      template: `<a load="ce1">ce-one</a><br><au-viewport></au-viewport>`,
    })
    export class MyApp {}
    import { customElement } from '@aurelia/runtime-html';
    import { IRouteViewModel, route, RouteNode } from '@aurelia/router-lite';
    
    @customElement({ name: 'ce-two', template: 'ce2 ${id1} ${id2}' })
    class CeTwo implements IRouteViewModel {
      private static id1: number = 0;
      private static id2: number = 0;
      private readonly id1: number = ++CeTwo.id1;
      private id2: number;
      public canLoad(): boolean {
        this.id2 = ++CeTwo.id2;
        return true;
      }
    }
    
    @customElement({ name: 'ce-one', template: 'ce1 ${id1} ${id2}' })
    class CeOne implements IRouteViewModel {
      private static id1: number = 0;
      private static id2: number = 0;
      private readonly id1: number = ++CeOne.id1;
      private id2: number;
      public canLoad(): boolean {
        this.id2 = ++CeOne.id2;
        return true;
      }
    }
    
    @route({
      transitionPlan(current: RouteNode, next: RouteNode) {
        return next.component.Type === CeTwo ? 'invoke-lifecycles' : 'replace';
      },
      routes: [
        {
          id: 'ce1',
          path: ['ce1'],
          component: CeOne,
        },
        {
          id: 'ce2',
          path: ['ce2'],
          component: CeTwo,
        },
      ],
    })
    @customElement({
      name: 'ro-ot',
      template: `
    <a load="ce1@$1+ce2@$2">ce1@$1+ce2@$2</a>
    <div id="content">
      <au-viewport name="$1"></au-viewport>
      <au-viewport name="$2"></au-viewport>
    </div>
    `,
    })
    export class MyApp {}
    Logo
    Logo
    Logo
    Logo

    Naming Components

    The component name, derived from the file name, must contain a hyphen when working with Shadow DOM (see Styling Components). This is part of the W3C Web Components standard and is designed to serve as a namespacing mechanism for custom HTML elements.

    A typical best practice is to choose a two to three-character prefix to use consistently across your app or company. For example, all components provided by Aurelia have the prefix au-.

    There are numerous ways in which you can create custom components. By leveraging conventions, you can create simple components with minimal code to more verbose components that offer greater control over how they work.

    There is no right or wrong way to create a component. As you will soon see, you can choose whatever works for your needs.

    Creating components

    In Aurelia, you can export a plain Javascript class, and it will assume that it is a component as a default convention-based setting. Components, by default, are no different to vanilla Javascript classes.

    This is what a basic component in Aurelia can look like. You would add logic and bindable properties (maybe), but a barebones component is just a class.

    export class AppLoader {
    }
    <p>Loading...</p>

    So far, we haven't written any Aurelia-specific code.

    Conventions mean Aurelia will automatically associate our component view model (app-loader.ts) with a file of the same name but with .html on the end, so app-loader.html.

    Don't Skip the Conventions

    We highly recommend that you leverage conventions where possible. A few benefits include the following:

    • Reduction of boilerplate.

    • Cleaner, more portable code.

    • Improved readability and learnability of code.

    • Less setup work and maintenance over time.

    • Ease of migration to future versions and platforms.

    Explicit component creation using @customElement

    If you don't want to use conventions, a @customElement decorator allows you to create custom elements verbosely. There are benefits to explicit component creation using the @customElement decorator, as conventions are bypassed.

    There are many reasons you might want to use the customElement decorator. It allows you to specify a different HTML template (or inline template string). You can define the name of the HTML tag used to reference the element, the stylings and other aspects of components that Aurelia would handle for you.

    Here is how you can avoid a separate HTML view file entirely and define the template inline:

    As you can see, we only have a view model now and specify the view template inline. For simple components that require a view model, it's a nice in-between way of creating components without cluttering your application with additional files.

    Configuring the customElement decorator

    We've seen that the customElement decorator allows us to be explicit on our components. Here are some of the valid configuration options for this decorator.

    name

    Allows you to configure what the HTML representation of the component will be. In the above example, specifying "app-loader" as the name of the component means we reference it in our views using <app-loader></app-loader>

    It makes no sense when you only want to configure the name to use the object notation. You can do this:

    template

    Referencing the app-loader example again allows you to specify your template contents. You can also use the template configuration property to specify no template by providing null as a value:

    If you don't specify a template property, Aurelia won't use conventions to find the template either.

    dependencies

    Components can have explicit dependencies declared from within the customElement decorator too. This means you do not have to import them from within your template using <import> , which can be a nice explicit way to handle dependencies.

    It is worth noting that dependencies can also be specified from within the template using the import tag, or they can be globally registered through Aurelia's Dependency Injection layer.

    Programmatic component creation

    Aurelia also has a more verbose API for creating components in your applications. You can use this approach to create components inline without needing separate files. This approach also works nicely for testing.

    By calling CustomElement.define we can create a component using familiar syntax to the verbose decorator approach above, including dependencies and more.

    While it is good to know what APIs Aurelia provides, in most instances, you won't need to define custom elements inside your Aurelia applications using the define functionality. This is helpful when writing tests, which you can learn about here.

    HTML only components

    More often than not, when you create a component in Aurelia, you want to create a view and view model-based component. However, Aurelia uniquely allows you to create components using just HTML.

    When working with HTML-only components, the file name becomes the HTML tag. Say you had a component called app-loader.html you would reference it using <app-loader></app-loader>

    Say you wanted to create a loader component that didn't require any logic and displayed a CSS loader. It might look something like this. We will call this app-loader.html

    To use this component, import and reference it:

    Aurelia infers the component's name from the file name (it strips off the .html file extension).

    HTML components with bindable properties

    Sometimes you want a custom element with bindable properties. Aurelia allows you to do this without needing a view model using the <bindable> custom element. The following is what you would have used the @bindable decorator for inside your view model if you had one.

    Using it in your application would look like this:

    This replaces the equivalent of a view model-based component, which would use the bindable decorator.

    Components without views

    If you worked with Aurelia 1, you might have been familiar with a feature @noView that allowed you to mark your components as viewless (they had a view model but no accompanying view). You probably think, "This sounds a lot like a custom attribute", but not quite. However, there are situations where a custom element without a view is needed.

    One prime example of a viewless component is a loading indicator using the nprogress library.

    In this example, the nprogress library handles adding and removing styles/elements from the DOM, so we omit the template. By choosing not to specify a template, this component will not have an accompanying view.

    While a viewless component is a very specific need, and in many cases, a custom attribute is a better option, omitting the template property of customElement you can achieve the same thing.

    Registering your components

    To enable the custom markup to be available within your template or globally, you must register your element. To use a component, you must register it globally or within the component, you would like to use it within.

    Globally registering an element

    To register your component to be used globally within your application, you will use .register in main.ts

    To learn more about working with Aurelia's Dependency Injection and registering dependencies, consult the Dependency Injection documentation to learn more.

    Importing the element within the template

    Adding your element to be used within the template is as easy as adding the following line in your .html file. Paths for the import element are relative, so ensure that your import paths are correct if you encounter any issues.

    Containerless components

    In some scenarios, you want the ability to use a component but remove the tags from your markup leaving behind the inner part and removing the outer custom element tags. You can achieve this using containerless functionality.

    When using containerless functionality, because the tags are removed from the DOM, you will lose the ability to reference the element container tags. This can make tasks such as using third-party libraries or writing tests more difficult as you lose the reference. We recommend avoiding the use of containerless where possible.

    Configuring the customElement

    If you are creating components using the customElement decorator, you can denote components are containerless using the containerless boolean configuration property.

    The containerless decorator

    Aurelia provides a convenient decorator to mark your components as containerless.

    When referencing our component using <my-component></my-component> Aurelia will strip out the tags and leave the inner part.

    Containerless element in views

    Inside of your views, you can denote a containerless component by using the <containerless> element. Unlike the other approaches, you specify this element inside your HTML views.

    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.

    A quick introduction to routing

    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.

    Register the router and create routes

    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:

    Create the HTML View

    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.

    Configuration

    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.

    Setting the title of your application

    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.

    Customizing the title

    Using the transformTitlemethod 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.

    Changing the router mode (hash and pushState routing)

    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.

    Configuring pushState routing

    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.

    Configuring route markup parsing using useHref

    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.

    Handling unknown components

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

    Create the fallback component

    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.

    Programmatically

    By using the fallback property on the customize method when we register the router, we can pass a component.

    Attribute

    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.

    Configuring the route swap order

    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.

    Configuration
    type ResolveCallback<T = any> = (handler?: IContainer, requestor?: IContainer, resolver?: IResolver) => T;
    app-loader.ts
    import { customElement } from 'aurelia';
    import template from './app-loader.html'; 
    
    @customElement({
        name: 'app-loader',
        template
    })
    export class AppLoader {
    }
    <p>Loading...</p>
    app-loader.ts
    import { customElement } from 'aurelia';
    
    @customElement({
        name: 'app-loader',
        template: '<p>Loading...</p>'
    })
    export class AppLoader {
    }
    import { customElement } from 'aurelia';
    
    @customElement('app-loader')
    export class AppLoader {
    }
    import { customElement } from 'aurelia';
    
    @customElement({
      name: 'app-loader',
      template: null
    })
    export class AppLoader {
    }
    import { customElement } from 'aurelia';
    import { NumberInput } from './number-input'; 
    
    @customElement({
      name: 'app-loader',
      dependencies: [ NumberInput ]
    })
    export class AppLoader {
    }
    import { CustomElement } from '@aurelia/runtime-html';
    
    export class App {
      MyField = CustomElement.define({
        name: 'my-input',
        template: '<input value.bind="value">'
      })
    }
    app-loader.html
    <p>Loading...</p>
    <import from="./app-loader.html"></import>
    
    <app-loader></app-loader>
    app-loader.html
    <bindable property="loading"></bindable>
    
    <p>${loading ? 'Loading...' : ''}</p>
    <import from="./app-loader.html"></import>
    
    <app-loader loading.bind="mybool"></app-loader>
    import nprogress from 'nprogress';
    import { bindable, customElement } from 'aurelia';
    
    import 'nprogress/nprogress.css';
    
    @customElement({
        name: 'loading-indicator'
    })
    export class LoadingIndicator {
      @bindable loading = false;
    
      loadingChanged(newValue) {
        if (newValue) {
          nprogress.start();
        } else {
          nprogress.done();
        }
      }
    }
    import Aurelia from 'aurelia';
    import { MyApp } from './my-app';
    import { SomeElement } from './path-to/some-element';
    
    Aurelia
      .register(SomeElement)
      .app(MyApp)
      .start();
    <import from="./path-to/some-element"></import>
    import { customElement, ICustomElementViewModel } from 'aurelia';
    
    @customElement({
        name: 'my-component',
        containerless: true
    })
    export class MyComponent implements ICustomElementViewModel {    
        constructor() {
    
        }
     }   
    import { ICustomElementViewModel } from 'aurelia';
    import { containerless } from '@aurelia/runtime-html';
    
    @containerless
    export class MyComponent implements ICustomElementViewModel {    
        constructor() {
    
        }
     }   
    <containerless>
    
    <p>My custom element markup</p>
    main.ts
    import Aurelia from 'aurelia';
    
    // Our router configuration import to register the Router with Aurelia's DI
    import { RouterConfiguration } from '@aurelia/router';
    
    import { MyApp } from './my-app';
    
    Aurelia
      .register(RouterConfiguration)
      .app(MyApp)
      .start();
    my-app.ts
    import { HomePage } from './home-page';
    
    export class MyApp {
        static routes = [
            {
                path: '',
                component: HomePage,
                title: 'Home'
            },
        ];
    }
    home-page.html
    <h1>Homepage</h1>
    <p>This is the homepage.</p>
    home-page.ts
    export class HomePage {
    }
    my-app.html
    <!-- Two routes using the load attribute containing the path of the route -->
    <a load="/">Home</a>
    
    <!-- This is where our routed components are loaded -->
    <au-viewport></au-viewport>
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router'; 
    
    Aurelia
      .register(
        RouterConfiguration.customize({ 
          title: '${componentTitles}${appTitleSeparator}My App'
        }))
      .app(component)
      .start();
    main.ts
    import { RouterConfiguration, RoutingInstruction, Navigation } from '@aurelia/router';
    import { Aurelia } from 'aurelia';
    
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router';
    import { MyApp } from './my-app';
    
    Aurelia
      .register(RouterConfiguration.customize({
          title: {
            transformTitle: (title: string, instruction: RoutingInstruction, navigation: Navigation) => {
              return `${title} - MYAPP`;
            }
          }
      })
      .app(MyApp)
      .start();
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router'; 
    
    Aurelia
      .register(RouterConfiguration.customize({ useUrlFragmentHash: false }))
      .app(component)
      .start();
    <head>
        <base href="/">
    </head>
    <a href="mailto:[email protected]">Email Me</a>
    <a href="mailto:[email protected]" external>Email Me</a>
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router'; 
    
    Aurelia
      .register(RouterConfiguration.customize({
          useHref: false
      }))
      .app(component)
      .start();
    missing-page.ts
    export class MissingPage {
      public static parameters = ['id'];
      public missingComponent: string;
    
      public loading(parameters: {id: string}): void {
        this.missingComponent = parameters.id;
      }
    }
    missing-page.html
    <h3>Ouch! I couldn't find '${missingComponent}'!</h3>
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router';
    import { MyApp } from './my-app';
    import { MissingPage } from './missing-page'; 
    
    Aurelia
      .register(RouterConfiguration.customize({
        fallback: MissingPage,
      }))
      .app(MyApp)
      .start();
    my-app.html
    <import from="./missing-page"></import>
    
    <au-viewport fallback="missing-page"></au-viewport>
    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.

    Installation

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

    Add routes and a viewport

    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.

    Enable routing

    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.

    Create some routes

    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.

    Create some components

    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.

    Listing the recipes

    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.

    Add in support for 404 fallback

    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.

    Reading route parameters

    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.

    Styling active links

    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.

    That's it

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

    Conditional Rendering

    There are two ways to show and hide content in Aurelia. Conditional rendering allows you to use boolean logic to show and hide things inside Aurelia applications.

    Aurelia supports two different ways of conditionally showing and hiding content.

    if.bind

    You can add or remove an element by specifying an if.bind on an element and passing in a true or false value.

    When if.bind is passed false Aurelia will remove the element and all of its children from view. When an element is removed, if it is a custom element or has any associated events, it will be cleaned up, thus freeing up memory and other resources they were using.

    In the following example, we pass a value called isLoading , which is populated whenever something is loading from the server. We will use it to show a loading message in our view.

    When isLoading is a truthy value, the element will be displayed and added to the DOM. When isLoading is falsy, the element will be removed from the DOM, disposing of any events or child components.

    else

    There is also a else binding that allows you to create if/else statements too. The if/else functionality works how you might expect. Like Javascript, it allows you to say, "If this, otherwise that."

    The else value must be used on an element directly proceeding if.bind , or it will not work.

    A note on caching behavior: By default, the if.bind feature will cache the view model/view of the element you are using if.bind . Not being aware of this default behavior can lead to confusing situations where the previous state is retained, especially on custom elements.

    Using verbose syntax, you can opt out of caching if this becomes a problem.

    When using the verbose syntax, value.bind is the boolean condition that triggers your if.bind condition and cache: false is what disables the cache. Only disable the cache if it becomes a problem.

    Be careful. Using if.bind takes your markup out of the flow of the page. This causes both reflow and repaint events in the browser, which can be intensive for large applications with a lot of HTML markup.

    show.bind

    You can conditionally show or hide an element by specifying a show.bind and passing in a true or false value.

    When show.bind is passed false , the element will be hidden, but unlike if.bind it will not be removed from the DOM. Any resources, events or bindings will remain. It's the equivalent of display: none; CSS, the element is hidden but not removed.

    In the following example, we are passing a value called isLoading , which is populated whenever something is loading from the server. We will use it to show a loading message in our view.

    When isLoading is a truthy value, the element will be visible. When isLoading is falsy, the element will be hidden but remain in view.

    switch.bind

    To deal with conditional rendering Aurelia also provides the switch template controller. It behaves like if/else in terms of that it does detach the elements from DOM, when the condition does not satisfy. The difference being is that it brings the intrinsic flexibility and semantics of using a switch statement with it.

    A typical use-case of switch involves dealing with enums. For example, let's consider the following Status enum.

    When tasked with displaying a specific text for a specific member (status) of the Status enum, with only if bind at our disposal, we may end up with the following markup.

    Also if there are new statuses added to the Status enum in future, this markup will end up more verbose, and possibly difficult to understand. Moreover, the semantics of the code might as well be somewhat lost. With the usage of the switch/case template controller, the above markup can be written as following.

    This behaves in similar fashion a switch in JavaScript behaves. That is it renders the first match, and ignores the rest. For example if the status has a value Status.processing, it will render <span>Processing your order.</span>. Note that it intrinsically avoids matching the following cases after the first match and consequently binding and rendering those elements. That is the basic and typical use-case of the switch/case template controllers. Now let's see some other features of this as well.

    default-case

    The switch also supports default-case; i.e. this "case" will be matched, if nothing else is matched.

    With this markup, if the status is set to Status.unknown or Status.delivered, <span>Unknown.</span> will be rendered.

    multi-case

    It is possible to map a single element to multiple cases, by binding an array to the case.

    For either of Status.received or Status.processing, it will render <span>Order received.</span>. A JavaScript equivalent of this would be the following.

    When an array is bound to the case, the value of the switch is matched against the items in the array and not with the array reference.

    fall-through

    It is also possible to have the switch-case fallthrough in the markup, where you don't want to break after a case has been executed. This means something like this.

    Aurelia equivalent of this will be the following.

    Assuming that status is set to Status.received, it will end up the rendering the first two <span>s.

    • By default for every case fallThrough is set to false. If needed, you need to set it to true explicitly. This the reason why we don't need to write the following: <span case="value.bind:'processing'; fall-through.bind: false">Processing your order.</span>.

    Miscellaneous examples

    This section includes few more interesting examples that you might encounter in real life, and the statutory warnings.

    • Another usage of switch that we often see in the wild, is to use a static expression for switch and more dynamic expression for case. Therefore, the following is a valid usage of switch.

    • The switch can be used to provide conditional projection to au-slot. The following markup is rendered as '<foo-bar> <span>Order received.</span> </foo-bar>' with status set to Status.received.

    • The case can be used with <au-slot> element as well. The following markup is rendered as '<foo-bar> <div> <span>Projection</span> </div> </foo-bar>' with status set to Status.received.

    • switchs can be nested. For example, the following markup is rendered as <span> Expected to be delivered in 2 days. </span> with status set to Status.delivered.

    • switch can work without any case attribute in it. However, the case cannot be used with the switch applied to its parent. This applies to the default-case as well.

    • In fact, it is worth noting that case should be the direct child of switch. For most of the cases Aurelia will throw error otherwise; for other cases, it might lead to unexpected results. If you think, any of the following should be supported, then let us know your use-case.

    Creating Routes

    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.

    Route syntax

    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 anatomy of a route path

    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 section to learn how to access them.

    Required named parameters

    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.

    Optional named parameters

    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

    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.

    Route configuration options

    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.

    Redirect

    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.

    Specify routes

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

    Create routes using a static property

    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.

    Defining routes using the route decorator

    The syntax for routes stays the same using the decorator. Just how they have defined changes slightly.

    Child routes

    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.

    Catch all / 404 not found route

    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:

    Lazy loaded routes

    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.

    Passing information between routes

    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.

    Styling Active Router Links

    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.

    Building plugins

    Aurelia makes it easy to create your own plugins. Learn how you can create individual plugins, register them and work with tasks to run code at certain parts of the lifecycle process.

    One of the most important needs of users is to design custom plugins. In the following, we want to get acquainted with how to design a plugin in the form of a mono-repository structure with configuration.

    What is a mono-repository?

    A monorepo (mono repository) is a single repository that stores all of your code and assets for every project. Using a monorepo is important for many reasons. It creates a single source of truth. It makes it easier to share code. It even makes it easier to refactor code.

    Lifecycle hooks

    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.

    my-app.html
    <div>
      <h1>Recipes</h1>
      
      <nav>
        <a load="/">Home</a>&nbsp;&nbsp;
        <a load="/recipes">Recipes</a>
      </nav>
      
      <au-viewport></au-viewport>
    </div>
    main.ts
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router';
    import { MyApp } from './my-app';
    
    Aurelia
      .register(RouterConfiguration.customize({ useUrlFragmentHash: false }))
      .app(MyApp)
      .start();
    export class MyApp {
      static routes = [
        {
          path: '',
          component: '',
          title: 'Home',
        },
        {
          path: 'recipes',
          component: '',
          title: 'Recipes',
        },
        {
          path: 'recipes/:recipeId',
          component: '',
          title: 'Recipe',
        },
      ];
    }
    home-page.ts
    export class HomePage {
    }
    home-page.html
    <p>Welcome to flavortown. Aurelia Recipes is the only recipe application you will need to manage your recipes.</p>
    recipes-page.ts
    export class RecipesPage {
    }
    recipes-page.html
    <p>Your recipes. In one place.</p>
    recipe-detail.ts
    export class RecipeDetail {
    }
    recipe-detail.html
    <p>This is a recipe.</p>
    my-app.ts
    import { HomePage } from './home-page';
    import { RecipesPage } from './recipes-page';
    import { RecipeDetail } from './recipe-detail';
    
    export class MyApp {
      static routes = [
        {
          path: '',
          component: HomePage,
          title: 'Home',
        },
        {
          path: '/recipes',
          component: RecipesPage,
          title: 'Recipes',
        },
        {
          path: '/recipes/:recipeId',
          component: RecipeDetail,
          title: 'Recipe',
        },
      ];
    }
    recipes-page.ts
    import { HttpClient } from '@aurelia/fetch-client';
    
    export class RecipesPage {
        private http: HttpClient = new HttpClient();
        private recipes = [];
        
        async bound() {
            const response = await this.http.fetch(`https://www.themealdb.com/api/json/v1/1/search.php?f=b`);
            
            const result = await response.json();
            
            this.recipes = result.meals;
        }
    }
    <ul>
        <li repeat.for="recipe of recipes"><a load="/recipes/${recipe.idMeal}">${recipe.strMeal}</a></li>
    </ul>
    fourohfour-page.ts
    export class 404Page {
    }
    fourohfour-page.html
    <h1>Oops!</h1>
    <p>Sorry, that link doesn't exist.</p>
    my-app.html
    <import from="./fourohfour-page"></import>
    
    <div>
      <h1>Recipes</h1>
    
      <nav>
        <a load="/">Home</a>&nbsp;&nbsp;&nbsp;&nbsp;
        <a load="/recipes">Recipes</a>
      </nav>
      <au-viewport fallback="fourohfour-page"></au-viewport>
    </div
    recipe-detail.ts
    import { HttpClient } from '@aurelia/fetch-client';
    import { IRouter } from '@aurelia/router';
    
    export class RecipeDetail {
      private http: HttpClient = new HttpClient();
      private recipe;
    
      constructor(@IRouter readonly router: IRouter) {}
    
      async canLoad(parameters) {
        if (parameters?.recipeId) {
          const loadRecipe = await this.loadRecipe(parameters.recipeId);
    
          if (loadRecipe) {
            this.recipe = loadRecipe;
            
            return true;
          } else {
            this.router.load(`/recipes`);
          }
        }
      }
    
      async loadRecipe(recipeId) {
        const request = await this.http.fetch(
          `https://www.themealdb.com/api/json/v1/1/lookup.php?i=${recipeId}`
        );
        const response = await request.json();
    
        return response.meals ? response.meals[0] : false;
      }
    }
    recipe-detail.html
    <h1>${recipe.strMeal}</h1>
    <img src.bind="recipe.strMealThumb" />
    <p textcontent.bind="recipe.strInstructions"></p>
    my-app.css
    .active {
      font-weight: bold;
    }
    
    a {
      color: #000;
      text-decoration: none;
    }

    fall-through: true is a less verbose syntax for binding the value of fallThrough. In this case, the string 'true' and 'false' are converted to boolean true, and false respectively.

    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.

  • Routing Lifecycle
    <div if.bind="isLoading">Loading...</div>
    <div if.bind="showThis">Hello, there.</div>
    <div else>Or else.</div>
    <some-element if="value.bind: showThis; cache: false"></some-element>
    <div show.bind="isLoading">Loading...</div>
    Status.ts
    const enum Status {
      received   = 'received',
      processing = 'processing',
      dispatched = 'dispatched',
      delivered  = 'delivered',
      unknown    = 'unknown',
    }
    my-app.html
    <span if.bind="status === 'received'">Order received.</span>
    <span if.bind="status === 'processing'">Processing your order.</span>
    <span if.bind="status === 'dispatched'">On the way.</span>
    <span if.bind="status === 'delivered'">Delivered.</span>
    my-app.html
    <template switch.bind="status">
      <span case="received">Order received.</span>
      <span case="processing">Processing your order.</span>
      <span case="dispatched">On the way.</span>
      <span case="delivered">Delivered.</span>
    </template>
    my-app.html
    <template switch.bind="status">
      <span case="received">Order received.</span>
      <span case="processing">Processing your order.</span>
      <span case="dispatched">On the way.</span>
      <span default-case>Unknown.</span>
    </template>
    my-app.html
    <template switch.bind="status">
      <span case.bind="['received', 'processing']">Order received.</span>
      <span case="dispatched">On the way.</span>
      <span case="delivered">Delivered.</span>
    </template>
    my-app.ts
    switch(status) {
      case Status.received:
      case Status.processing:
        return 'Order received.';
      case Status.dispatched:
        return 'On the way.';
      case Status.delivered:
        return 'Delivered.';
    }
    my-app.ts
    let ret: string = "";
    switch(status) {
      case Status.received:
        ret = 'Order received.';
      case Status.processing:
        ret =`${ret}, Order received.`;
        break;
      case Status.dispatched:
        ret = 'On the way.';
        break;
      case Status.delivered:
        ret = 'Delivered.';
        break;
    }
    return ret;
    my-app.html
    <template switch.bind="status">
      <span case="value.bind:'received'; fall-through.bind: true">Order received.</span>
      <span case="processing">Processing your order.</span>
      <span case="dispatched">On the way.</span>
      <span case="delivered">Delivered.</span>
    </template>
    my-app.html
    <template>
      <template repeat.for="num of 100">
        <template switch.bind="true">
          <span case.bind="num % 3 === 0 && num % 5 === 0">FizzBuzz</span>
          <span case.bind="num % 3 === 0">Fizz</span>
          <span case.bind="num % 5 === 0">Buzz</span>
        </template>
      </template>
    </template>
    my-app.html
    <template as-custom-element="foo-bar">
      <au-slot name="s1"></au-slot>
    </template>
    
    <foo-bar>
      <template au-slot="s1">
        <template switch.bind="status">
          <span case="received">Order received.</span>
          <span case="dispatched">On the way.</span>
          <span case="processing">Processing your order.</span>
          <span case="delivered">Delivered.</span>
        </template>
      </template>
    </foo-bar>
    my-app.html
    <template as-custom-element="foo-bar">
      <bindable property="status"></bindable>
      <div switch.bind="status">
        <au-slot name="s1" case="received">Order received.</au-slot>
        <au-slot name="s2" case="dispatched">On the way.</au-slot>
        <au-slot name="s3" case="processing">Processing your order.</au-slot>
        <au-slot name="s4" case="delivered">Delivered.</au-slot>
      </div>
    </template>
    
    <foo-bar status.bind="status">
      <span au-slot="s1">Projection</span>
    </foo-bar>
    my-app.html
    <template>
      <let day.bind="2"></let>
      <template switch.bind="status">
        <span case="received">Order received.</span>
        <span case="dispatched">On the way.</span>
        <span case="processing">Processing your order.</span>
        <span case="delivered" switch.bind="day">
          Expected to be delivered
          <template case.bind="1">tomorrow.</template>
          <template case.bind="2">in 2 days.</template>
          <template default-case>in few days.</template>
        </span>
      </template>
    </template>
    my-app.html
    <!-- this works! -->
    <div switch.bind="status">
      the curious case of \${status}
    </div>
    
    <!-- this throws error! -->
    <span case="foo"></span>
    my-app.html
    <!-- These do NOT work! -->
    <!-- #1: usage with `if` -->
    <template>
      <template switch.bind="status">
        <template if.bind="true">
          <span case="delivered">delivered</span>
        </template>
      </template>
    </template>
    
    <!-- #2: usage with `repeat.for` -->
    <template>
      <template switch.bind="status">
        <template repeat.for="s of ['received','dispatched','processing','delivered',]">
          <span case.bind="s">\${s}</span>
        </template>
      </template>
    </template>
    
    <!-- #3: usage with `au-slot` -->
    <template as-custom-element="foo-bar">
      <au-slot name="s1"></au-slot>
    </template>
    
    <foo-bar switch.bind="status">
      <template au-slot="s1">
        <span case="dispatched">On the way.</span>
        <span case="delivered">Delivered.</span>
      </template>
    </foo-bar>
    
    <!--
      The following example does produce some sort of output;
      but such usage is not supported.
    -->
    <template as-custom-element="foo-bar">
      foo bar
    </template>
    
    <template switch.bind="status">
      <foo-bar>
        <span case="dispatched">On the way.</span>
        <span case="delivered">Delivered.</span>
      </foo-bar>
    </template>
    <!--
      With `status` set to 'dispatched' it produces this output:
      `<foo-bar> <span>On the way.</span> foo bar </foo-bar>`
    -->
    {
        path: 'my-route',
        component: import('./my-component')
    }
    import { IRouteableComponent, IRoute } from '@aurelia/router';
    
    export class MyApp implements IRouteableComponent {
      static routes: IRoute[] = [
        {
          path: 'product/:productId',
          component: import('./components/product-detail')
        }
      ]
    }
    import { IRouteableComponent, IRoute } from '@aurelia/router';
    
    export class MyApp implements IRouteableComponent {
      static routes: IRoute[] = [
        {
          path: 'product/:productId/:variation?',
          component: import('./components/product-detail')
        }
      ]
    }
    import { IRouteableComponent, IRoute } from '@aurelia/router';
    
    export class MyApp implements IRouteableComponent {
      static routes: IRoute[] = [
        {
          path: 'files/*path',
          component: import('./components/files-manager')
        }
      ]
    }
    @routes([
        { path: '', redirectTo: 'products' },
        { path: 'products', component: import('./products'), title: 'Products' },
        { path: 'product/:id', component: import('./product'), title: 'Product' }
    ])
    export class MyApp {
    
    }
    import { IRouteableComponent, IRoute } from '@aurelia/router';
    import { HomePage } from './components/home-page';
    
    export class MyApp implements IRouteableComponent {
      static routes: IRoute[] = [
        {
          path: ['', 'home'],
          component: HomePage,
          title: 'Home',
        }
      ]
    }
    import { IRouteableComponent, IRoute } from '@aurelia/router';
    
    export class MyApp implements IRouteableComponent {
      static routes: IRoute[] = [
        {
          path: ['', 'home'],
          component: import('./components/home-page'),
          title: 'Home',
        }
      ]
    }
    import { IRouteableComponent, routes } from '@aurelia/router';
    
    @routes([
        {
            path: ['', 'home'],
            component: import('./components/home-page'),
            title: 'Home',
        }
    ])
    export class MyApp implements IRouteableComponent {
    
    }
    my-app.ts
    export class MyApp {
        static routes = [
            {
                path: '/dashboard',
                component: () => import('./dashboard-page'),
                title: 'Dashboard'
            }
        ];
    }
    dashboard-page.ts
    import { DashboardHome } from './dashboard-home';
    
    import { IRouteableComponent } from '@aurelia/router';
    
    export class DashboardPage implements IRouteableComponent {
        static routes = [
            {
                path: '',
                component: DashboardHome,
                title: 'Landing'
            }
        ];
    }
    dashboard-page.html
    <div>
        <au-viewport></au-viewport>
    </div>
    dashboard-home.ts
    export class DashboardHome {
        
    }
    dashboard-home.html
    <h1>Dashboard</h1>
    <p>Welcome to your dashboard.</p>
    {
        path: '*',
        redirectTo: '/products'
    }
    {
        path: '*',
        component: () => import('./not-found')
    }
        {
          path: 'product/:productId',
          component: () => import('./components/product-detail')
        }
    @routes([
        {
          id: 'home',
          path: '',
          component: import('./home'),
          title: 'Home'
        },
        {
          path: 'product/:id',
          component: import('./product'),
          title: 'Product',
          data: {
              requiresAuth: false
          }
        }
    ])
    export class MyApp {
    
    }
    .active {
        font-weight: bold;
    }
    <a load="about">About</a>
    <a load="home">Home</a>
    How NPM v7 helps us?

    With workspaces. Workspaces are a set of features in the npm CLI that offer support for managing multiple packages within a single top-level, root package. NPM v7 has shipped with Node.js v15.

    What is the scenario?

    To move forward with a practical example. We want to implement Bootstrap components in a custom mono-repository with a configuration to make it customizable.

    What is the library structure?

    We want to separate our plugin into three packages.

    • bootstrap-v5-core

    We will add the Bootstrap 5 configurations to this package.

    • bootstrap-v5

    Our Bootstrap 5 components will define in this package. bootstrap-v5 depends on bootstrap-v5-core packages.

    • demo

    We will use our plugin in this package as a demo. demo depends on bootstrap-v5-core and bootstrap-v5.

    How to configure NPM v7 workspaces?

    To configure your monorepo, you should do as following:

    Make sure you have installed NPM v7+

    Go to a folder that you want to make the project, for example my-plugin

    Create a packages folder and package.json inside it.

    The mono repository's name is @my-plugin. We defined our workspaces (projects) under packages folder.

    Open your packages folder and install the projects inside it.

    After creating, delete all files inside src folders of bootstrap-v5-core and bootstrap-v5 but resource.d.ts. We will add our files there.

    How to manage dependencies?

    As described in the structure section defined packages depend on each other. So, we link them together and add the other prerequisites for each. At the same time it is good to name them a bit better.

    • bootstrap-v5-core

    Go to its package.json and change the name to

    As our core package, it has no dependency.

    • bootstrap-v5

    Go to its package.json and change the name to

    Then, add the following dependencies:

    • demo

    Go to its package.json and change the name to

    Then, add the following dependencies:

    Note: All created packages have 0.1.0 version so pay attention if the version changes, update it correctly.

    Run the command below to install packages inside the my-plugin folder.

    How to define a plugin configuration?

    Go to the src folder of bootstrap-v5-core package and create each of the below files there.

    Size

    As I mentioned before, I want to write a configurable Bootstrap plugin so create src/Size.ts file.

    I made a Size enum to handle all Bootstrap sizes. I want to make an option for those who use the plugin to define a global size for all Bootstrap components at first. Next, we manage our components according to size value.

    Bootstrap 5 Options

    Create src/BootstrapV5Options.ts file.

    You need to define your configurations via an interface with its default values as a constant.

    DI

    To register it via DI, you need to add codes below too:

    configure helps us to set the initial options or loading special files based on a specific option to DI system. To load specific files, you need to do this via AppTask.

    The AppTask allows you to position when/where certain initialization should happen and also optionally block app rendering accordingly.

    If you no need this feature replace it with:

    Registration.instance helps us to register our default option into the container.

    To use your option later inside your components, you should introduce it via DI.createInterface. The trick here is to create a resource name the same as what you want to inject. This is the reason I name it as IBootstrapV5Options constant.

    Finally, you need to make sure that the user can determine the settings. This is the task of BootstrapV5Configuration.

    register This method helps the user to use your plugin with default settings but customize is the method that allows the user to introduce their custom settings.

    Exports

    Create src/index.ts file.

    Create new index.ts file inside bootstrap-v5-core package too.

    How to implement the custom plugin?

    Go to the src folder of bootstrap-v5 package, create a button folder then create each of the below files there.

    • View

    Create bs-button.html file.

    • ViewModel

    Create bs-button.ts file.

    As you can see we are able to access to plugin options easy via ctor (DI) and react appropriately to its values.

    In this example, I get the size from the user and apply it to the button component. If the user does not define a value, the default value will be used.

    Exports

    Create files below correctly:

    Create src/button/index.ts file.

    Create src/index.ts file.

    Create new index.ts file inside bootstrap-v5 package.

    How to use it?

    Open demo package and go to the src and update main.ts.

    Importing is available for whole components

    Or just a component

    To register your components you should add them to register method.

    We support configuration so we should introduce it to register method too.

    Now, You are able to use your bs-button inside src/my-app.html.

    To run the demo easily, go to the my-plugin root folder and add the following script section to the package.json.

    Then, call the command

    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.

    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.

    You can also see this example in action below.

    Redirect to another view from 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.

    Accessing fragment and query

    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.

    Order of invocations

    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.

    Custom attributes

    Using built-in custom attributes and building your own.

    A custom attribute allows you to create special properties to enhance and decorate existing HTML elements and components. Natively attributes exist in the form of things such as disabled on form inputs or aria text labels. Where custom attributes can be especially useful is wrapping existing HTML plugins that generate their own markup.

    Creating custom attributes

    On a simplistic level, custom attributes resemble quite a lot. They can have , and they use classes for their definitions.

    A basic custom attribute looks something like this:

    router-lite - tr-plan - function - sibling - StackBlitzStackBlitz
    router-lite - tr-plan - function - StackBlitzStackBlitz
    router-lite - tr-plan - replace-inheritance - StackBlitzStackBlitz
    npm -v
    // package.json content
    
    {
      "name": "@my-plugin",
      "workspaces": [
        "packages/**"
      ]
    }
    npx makes aurelia bootstrap-v5-core -s typescript
    npx makes aurelia bootstrap-v5 -s typescript
    npx makes aurelia demo -s typescript
    "name": "@my-plugin/bootstrap-v5-core"
    "name": "@my-plugin/bootstrap-v5"
    // bootstrap-v5/package.json
    "dependencies": {    
        "aurelia": "latest",
        "bootstrap": "^5.0.0-beta2",    
        "@my-plugin/bootstrap-v5-core": "0.1.0"
    },
    "name": "@my-plugin/demo"
    // demo/package.json
    "dependencies": {    
        "aurelia": "latest",    
        "@my-plugin/bootstrap-v5-core": "0.1.0",
        "@my-plugin/bootstrap-v5": "0.1.0"
    },
    npm install
    // Size.ts
    
    export enum Size {
        ExtraSmall = 'xs',
        Small = 'sm',
        Medium = 'md',
        Large = 'lg',
        ExtraLarge = 'xl',
    }
    // BootstrapV5Options.ts
    
    import { Size } from "./Size";
    
    export interface IBootstrapV5Options {
        defaultSize?: Size;
    }
    const defaultOptions: IBootstrapV5Options = {
        defaultSize: Size.Medium
    };
    // BootstrapV5Options.ts
    
    import { IContainer } from '@aurelia/kernel';
    import { AppTask, DI, Registration } from 'aurelia';
    
    function configure(container: IContainer, config: IBootstrapV5Options = defaultOptions) {
        return container.register(
            AppTask.hydrating(IContainer, async container => {
                if (config.enableSpecificOption) {
                    const file = await import('file');
                    cfg.register(Registration.instance(ISpecificOption, file.do());
                }
                Registration.instance(IBootstrapV5Options, config).register(container);
            })
        );
    }
    
    export const IBootstrapV5Options = DI.createInterface<IBootstrapV5Options>('IBootstrapV5Options');
    
    export const BootstrapV5Configuration = {
        register(container: IContainer) {
            return configure(container);
        },
        customize(config: IBootstrapV5Options) {
            return {
                register(container: IContainer) {
                    return configure(container, config);
                },
            };
        }
    };
    function configure(container: IContainer, config: IBootstrapV5Options = defaultOptions) {
        Registration.instance(IBootstrapV5Options, config).register(container);
    }
    // index.ts
    
    export * from './BootstrapV5Configuration';
    export * from './Size';
    export * from './src';
    <button class="btn btn-primary btn-${size}" ref="bsButtonTemplate">
        Primary Button
    </button>
    import { customElement, containerless, BindingMode, bindable } from "aurelia";
    import template from "./bs-button.html";
    import { IBootstrapV5Options, Size } from "@my-plugin/bootstrap-v5-core";
    
    @customElement({ name: "bs-button", template })
    @containerless
    export class BootstrapButton {
        private bsButtonTemplate: Element;
        @bindable({ mode: BindingMode.toView }) public size?: Size = null;
        constructor(
            @IBootstrapV5Options private options: IBootstrapV5Options
        ) {
        }
        attached() {
            this.applySize();
        }
        private applySize() {
            if (this.options.defaultSize && !this.size) {
                switch (this.options.defaultSize) {
                    case Size.ExtraSmall:
                    case Size.Small:
                        this.resetSize();
                        this.size = Size.Small;
                        break;
                    case Size.Large:
                    case Size.ExtraLarge:
                        this.resetSize();
                        this.size = Size.Large;
                        break;
                    default:
                        this.resetSize();
                        this.size = Size.Medium;
                }
            }
        }
        private resetSize() {
            this.bsButtonTemplate.classList.remove("btn-sm", "btn-lg");
        }
    }
    @IBootstrapV5Options private options: IBootstrapV5Options
    export * from './bs-button';
    export * from './button';
    import 'bootstrap/dist/css/bootstrap.min.css';
    export * from './src';
    // main.ts
    
    import Aurelia from 'aurelia';
    import { MyApp } from './my-app';
    import { BootstrapV5Configuration } from '@my-plugin/bootstrap-v5-core';
    import * as BsComponents from '@my-plugin/bootstrap-v5';
    
    Aurelia
      .register(BsComponents, BootstrapV5Configuration)
      .app(MyApp)
      .start();
    import * as BsComponents from '@my-plugin/bootstrap-v5';
    import { BootstrapButton } from '@my-plugin/bootstrap-v5';
    .register(BsComponents) // For whole components
    // Or
    .register(BootstrapButton) // For a component
     // With default options
    .register(BootstrapV5Configuration)
    // Or with a custom option
    .register(BootstrapV5Configuration.customize({
      defaultSize: Size.Small // Components loads with small size.
    }))
    <bs-button></bs-button>
    <bs-button size="lg"></bs-button>
    {
      "name": "@my-plugin",
      "workspaces": [
        "packages/**"
      ],
      "scripts": {
        "start": "npm run --prefix packages/demo start"
      }
    }
    npm run start
    npm start
    import {
      IRouteViewModel,
      Params,
      RouteNode,
      NavigationInstruction,
    } from '@aurelia/router-lite';
    
    export class MyComponent implements IRouteViewModel {
      canLoad?(
        params: Params,
        next: RouteNode,
        current: RouteNode | null
      ): boolean
        | NavigationInstruction
        | NavigationInstruction[]
        | Promise<boolean | NavigationInstruction | NavigationInstruction[]>;
      loading?(params: Params, next: RouteNode, current: RouteNode | null): void | Promise<void>;
      canUnload?(next: RouteNode | null, current: RouteNode): boolean | Promise<boolean>;
      unloading?(next: RouteNode | null, current: RouteNode): void | Promise<void>;
    }
    import { Params } from '@aurelia/router-lite';
    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-lite';
    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-lite';
    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-lite';
    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 { NavigationInstruction, Params, RouteNode } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'c-one',
      template: `c1 \${id} fragment: \${fragment}`,
    })
    export class ChildOne {
      private id: number;
      private fragment: string;
      public canLoad(
        params: Params,
        next: RouteNode
      ): boolean | NavigationInstruction {
        this.fragment = next.fragment;
        const query = next.queryParams;
        const rawId = query.get('id');
        const redirectPath = `c2?${next.queryParams.toString()}${
          next.fragment ? `#${next.fragment}` : ''
        }`;
        if (rawId === null) return redirectPath;
        const id = Number(rawId);
        if (!Number.isInteger(id) || id % 2 != 0) return redirectPath;
        this.id = id;
        return true;
      }
    }
    import { IRouteViewModel, Params, RouteNode } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'c-one',
      template: `c1 \${msg}`,
    })
    export class ChildOne implements IRouteViewModel {
      private msg: string;
      public loading(params: Params, next: RouteNode) {
        this.msg = `loaded with id: ${params.id}`;
        next.title = 'Child One';
      }
    }
    import { IRouteViewModel, Params, RouteNode } from '@aurelia/router-lite';
    import { IPlatform } from '@aurelia/runtime-html';
    
    export class ChildOne implements IRouteViewModel {
    
      public constructor(@IPlatform private readonly platform: 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()}`
      );
    }
    Logo
    Logo
    Logo

    If you were to replace CustomAttribute with CustomElement, it would be a component. On a core level, custom attributes are a more primitive component form.

    Let's create a custom attribute that adds a red background and height to any dom element it is used on:

    Now, let's use our custom attribute:

    We import our custom attribute so the DI knows about it and then use it on an empty DIV. We'll have a red background element with a height of one hundred pixels.

    Explicit custom attributes

    The customAttribute decorator allows you to create custom attributes, including the name explicitly. Other configuration options include the ability to create aliases.

    Explicit attribute naming

    You can explicitly name the custom attribute using the name configuration property.

    Attribute aliases

    The customAttribute allows you to create one or more aliases that this attribute can go by.

    We can now use our custom attribute using the registered name red-square as well as redify and redbox the following example highlights using both aliases on an element.

    Single value binding

    Sometimes, you want a custom attribute with only one bindable property. You don't need to define the bindable property explicitly to do this, as Aurelia supports custom attributes with single-value bindings.

    The value property is automatically populated if a value is supplied to a custom attribute, however, requires you to define the value property as a bindable explicitly.

    When the value is changed, we can access it like this:

    Accessing the element

    When using the custom attribute on a dom element, there are instances where you want to be able to access the element itself. To do this, you can use the INode decorator and HTMLElement interface to inject and target the element.

    The code above was lifted from the first example, allowing us to access the element itself on the class using this.element This is how we can set CSS values and perform other modifications, such as initialising third-party libraries like jQuery and other libraries.

    Custom attributes with bindable properties

    In many cases, you might only need custom attributes without user-configurable properties. However, in some cases, you want the user to be able to pass in one or more properties to change the behavior of the custom attribute (like a plugin).

    Using bindable properties, you can create a configurable custom attribute. Taking our example from above, let's make the background color configurable instead of always red. We will rename the attribute for this.

    We can now provide a color on a per-use basis. Let's go one step further and allow the size to be set too.

    Responding to bindable property change events

    We have code that will work on the first initialization of our custom property, but if the property is changed after rendering, nothing else will happen. We need to use the change detection functionality to update the element when any bindable properties change.

    As a default convention, bindable property change callbacks will use the bindable property name followed by a suffix of Changed at the end. The change callback gets two parameters, the new value and the existing value.

    Want to learn more about bindable properties and how to configure them? Please reference the bindable properties section.

    Whenever our size or color bindable properties change, our element will be updated accordingly instead of only at render.

    Options binding

    Options binding provides a custom attribute with the ability to have multiple bindable properties. Each bindable property must be specified using the bindable decorator. The attribute view model may implement an optional ${propertyName}Changed(newValue, oldValue) callback function for each bindable property.

    When binding to these options, separate each option with a semicolon and supply a binding command or literal value, as in the example below. It is important to note that bindable properties are converted to dash-case when used in the DOM, while the view model property they are bound to is kept with their original casing.

    To use options binding, here is how you might configure those properties:

    Specifying a primary property

    When you have more than one bindable property, you might want to specify which property is the primary one (if any). If you mostly expect the user only to configure one property most of the time, you can specify it is the primary property through the bindable configuration.

    The above example specifies that color is the primary bindable property. Our code actually doesn't change at all. The way we consume the custom attribute changes slightly.

    Or, you can bind the value itself to the attribute:

    components
    bindable properties
    export class CustomPropertyCustomAttribute {
    }
      import { INode } from 'aurelia';
      
      export class RedSquareCustomAttribute {
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = '100px';
            this.element.style.backgroundColor = 'red';
        }
      }
    <import from="./red-square"></import>
    
    <div red-square></div>
      import { customAttribute, INode } from 'aurelia';
      
      @customAttribute({ name: 'red-square' }) 
      export class RedSquare {
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = '100px';
            this.element.style.backgroundColor = 'red';
        }
      }
      import { customAttribute, INode } from 'aurelia';
      
      @customAttribute({ name: 'red-square', aliases: ['redify', 'redbox'] }) 
      export class RedSquare {
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = '100px';
            this.element.style.backgroundColor = 'red';
        }
      }
    <div redify></div>
    
    <div redbox></div>
      import { INode } from 'aurelia';
    
      export class RedSquareCustomAttribute {
        private value;
        
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = '100px';
            this.element.style.backgroundColor = 'red';
        }
        
        bind() {
            this.element.style.backgroundColor = this.value;
        }
      }
      import { bindable, INode } from 'aurelia';
      
      export class RedSquareCustomAttribute {
        @bindable() private value;
        
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = '100px';
            this.element.style.backgroundColor = 'red';
        }
        
        bound() {
            this.element.style.backgroundColor = this.value;
        }
        
        valueChanged(newValue: string, oldValue: string){
            this.element.style.backgroundColor = newValue;
        }
      }
      import { INode } from 'aurelia';
      
      export class RedSquareCustomAttribute {
        constructor(@INode private element: HTMLElement){
    
        }
      }
      import { bindable, INode } from 'aurelia';
      
      export class ColorSquareCustomAttribute {
        @bindable() color: string = 'red';
      
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = '100px';
            this.element.style.backgroundColor = this.color;
        }
        
        bound() {
          this.element.style.backgroundColor = this.color;
        }
      }
      import { bindable, INode } from 'aurelia';
      
      export class ColorSquareCustomAttribute {
        @bindable() color: string = 'red';
        @bindable() size: string = '100px';
      
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = this.size;
            this.element.style.backgroundColor = this.color;
        }
        
        bound() {
          this.element.style.width = this.element.style.height = this.size;
          this.element.style.backgroundColor = this.color;
        }
    }
      import { bindable, INode } from 'aurelia';
      
      export class ColorSquareCustomAttribute {
        @bindable() color: string = 'red';
        @bindable() size: string = '100px';
      
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = this.size;
            this.element.style.backgroundColor = this.color;
        }
        
        bound() {
          this.element.style.width = this.element.style.height = this.size;
          this.element.style.backgroundColor = this.color;
        }
        
        colorChanged(newColor, oldColor) {
          this.element.style.backgroundColor = newColor;
        }
        
        sizeChanged(newSize: string, oldSize: string) {
          this.element.style.width = this.element.style.height = newSize;
        }
    }  
      import { bindable, INode } from 'aurelia';
      
      export class ColorSquareCustomAttribute {
        @bindable() color: string = 'red';
        @bindable() size: string = '100px';
      
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = this.size;
            this.element.style.backgroundColor = this.color;
        }
        
        bound() {
          this.element.style.width = this.element.style.height = this.size;
          this.element.style.backgroundColor = this.color;
        }
        
        colorChanged(newColor, oldColor) {
          this.element.style.backgroundColor = newColor;
        }
        
        sizeChanged(newSize: string, oldSize: string) {
          this.element.style.width = this.element.style.height = newSize;
        }
    }  
    <import from="./color-square"></import>
    
    <div color-square="color.bind: myColor; size.bind: mySize;"></div>
      import { bindable, INode } from 'aurelia';
       
      export class ColorSquareCustomAttribute {
        @bindable( {primary: true} ) color: string = 'red';
        @bindable() size: string = '100px';
      
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = this.size;
            this.element.style.backgroundColor = this.color;
        }
        
        bound() {
          this.element.style.width = this.element.style.height = this.size;
          this.element.style.backgroundColor = this.color;
        }
        
        colorChanged(newColor, oldColor) {
          this.element.style.backgroundColor = newColor;
        }
        
        sizeChanged(newSize, oldSize) {
          this.element.style.width = this.element.style.height = newSize;
        }
    }  
    <import from="./color-square"></import>
    
    <div color-square="blue"></div>
    <import from="./color-square"></import>
    
    <div color-square.bind="myColour"></div>

    Router configuration

    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.

    Choose between hash and pushState routing using 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 ) 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.

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

    Customizing title

    A buildTitle function can be used to customize the . 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 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.

    Enable or disable the usage of the 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 .

    Or, you can set useHref to false (default is true) and only ever use the load attribute for routes.

    Configure browser history strategy

    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.

    Override configured history strategy

    You can use the to override the configured history strategy for individual routing instructions.

    Configure active class

    Using the activeClass option you can add a class name to the router configuration. This class name is used by the 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.

    Watching data

    Watching data for changes, including support for expressions where you want to watch for changes to one or more dependencies and react accordingly.

    Introduction

    Unlike other observation strategies detailed in the observation section, the @watch functionality allows you to watch for changes in your view models. If you worked with the computedFrom decorator in Aurelia 1, the watch decorator is what you would replace it with.

    Template syntax & features

    Learn all there is to know about Aurelia's HTML templating syntax and features.

    Aurelia uses an HTML-based syntax for templating, allowing you to build applications in an intuitive way. All Aurelia templates are valid spec-compliant HTML that works in all browsers and HTML parsers.

    Text Interpolation

    String interpolation allows you to display values within your template views. By leveraging ${} which is a dollar sign followed by opening and closing curly braces, you can display values inside of your views. The syntax will be familiar to you if you are familiar with .

    Watching values with @watch

    Aurelia provides a way to author your components in a reactive programming model with the @watch decorator. Decorating a class or a method inside it with the @watch decorator, the corresponding method will be called whenever the watched value changes.

    Intended usages

    • The @watch decorator can only be used on custom element and attribute view models.

    • Corresponding watchers of @watch decorator will be created once, bound after binding, and unbound before unbinding lifecycles. This means mutation during binding/ after unbinding won't be reacted to, as watchers haven't started or have stopped.

    APIs

    Name
    Type
    Description

    expressionOrPropertyAccessFn

    string | IPropertyAccessFn

    Watch expression specifier

    changeHandlerOrCallback

    string | IWatcherCallback

    The callback that will be invoked when the value evaluated from watch expression has changed. If a name is given, it will be used to resolve the callback ONCE. This callback will be called with 3 parameters: (1st) new value from the watched expression. (2nd) old value from the watched expression (3rd) the watched instance. And the context of the function call will be the instance, same with the 3rd parameter.

    Reacting to property changes with @watch

    The @watch decorator allows you to react to changes on a property or computed function.

    In the following example, we will call a function every time the name property in our view model is changed.

    This is referred to in Aurelia as an expression. You can also observe properties on things like arrays for changes which might be familiar to you if you worked with Aurelia 1 and the @computedFrom decorator.

    This is what an expression looks like observing an array length for changes:

    Using computed functions to react to changes

    Sometimes you want to watch multiple values in a component, so you need to create an expression. A computed function is a function provided to the @watch decorator, which allows you to do comparisons on multiple values.

    To illustrate how you can do this, here is an example:

    In this example, the log method of PostOffice will be called whenever a new package is added to, or an existing package is removed from the packages array. The first argument of our callback function is the view model, allowing us to access class properties and methods.

    Usage examples

    Decorating on a class, string as watch expression, with arrow function as a callback

    Decorating on a class, string as watch expression, with method name as a callback

    The method name will be used to resolve the function once, which means changing the method after the instance has been created will not be recognized.

    Decorating on a class, string as watch expression, with normal function as a callback

    Decorating on a class, normal function as watch expression, with arrow function as callback

    Decorating on a class, arrow function as watch expression, with arrow function as callback

    Decorating on a method, string as watch expression

    Decorating on a method, normal function as watch expression

    Decorating on a method, arrow function as watch expression

    @watch reactivity examples

    During binding lifecycle, bindings created by @watch decorator haven't been activated yet, which means mutations won't be reacted to:

    There will be no log in the console.

    During bound lifecycle, bindings created by @watch decorator have been activated, and mutations will be reacted to:

    There will be 1 log in the console that looks like this: packages changes: 0 -> 1.

    Other lifecycles that are invoked after binding and before unbinding are not sensitive to the working of the @watch decorator, and thus don't need special mentions. Those lifecycles are attaching, attached, and detaching.

    During detaching lifecycle, bindings created by @watch decorator have not been de-activated yet, and mutations will still be reacted to:

    There will be 1 log in the console that looks like this: packages changes: 0 -> 1.

    During unbinding lifecycle, bindings created by @watch decorator have been deactivated, and mutations won't be reacted to:

    There will be no log in the console.

    How it works

    By default, a watcher will be created for a @watch() decorator. This watcher will start observing before bound lifecycle of components. How the observation works will depend on the first parameter given.

    • If a string or a symbol is given, it will be used as an expression to observe, similar to how an expression in Aurelia templating works.

    • If a function is given, it will be used as a computed getter to observe dependencies and evaluate the value to pass into the specified method. Two mechanisms can be employed:

      • For JavaScript environments with native proxy support: Proxy will be used to trap & observe property read. It will also observe collections (such as an array, map and set) based on invoked methods. For example, calling .map(item => item.value) on an array should observe the mutation of that array and the property value of each item inside the array.

      • For environments without native proxy support: the 2nd parameter inside the computed getter can be used to observe (or register) dependencies manually. This is the corresponding watcher created from a @watch decorator. It has the following interface:

        An example is:

        The firstName and lastName properties of contact components are being observed manually. And every time either firstName, or lastName change, the computed getter is run again, and the dependencies will be observed again. Observers are cached, and the same observer won't be added more than once, old observers from the old computed getter run will also be disposed of, so you won't have to worry about stale dependencies or memory leaks.

    Automatic array observation

    • By default, in the computed getter, array mutation method such as .push(), .pop(), .shift(), .unshift(), .splice(), and .reverse() are not observed, as there are no clear indicators of the dependencies to collect from those methods.

    Best practices

    Avoid mutations on dependencies inside a computed getter

    It is best to avoid mutation on dependencies collected inside a computed getter.

    Be careful with objects not access from the first parameter

    To ensure identity equality with proxies, always be careful with objects not accessed from the first parameter passed into the computed getter. Better get the raw underlying object before doing the strict comparison with ===.

    In this example, even if options on a MyClass instance has never been changed, the comparison of myClass.options === defaultOptions will still return false, as the actual value for myClass.options is a proxied object wrapping the real object, and thus is always different with defaultOptions.

    Don't return promises or async functions

    Dependency tracking inside a watch computed getter is done synchronously, which means returning a promise or having an async function won't work properly.

    Don't do the following:

    Displaying values with interpolation

    Interpolation can be used to display the value of variables within your HTML templates, object properties and other forms of valid data.

    To show how interpolation works, here is an example.

    Notice how the variable we reference in our HTML template is the same as it is defined inside of our view model? Anything specified on our view model class is accessible in the view. Aurelia will replace ${myName} with Aurelia think of it as a fancy string replacement. All properties defined in your view model will be accessible inside your templates.

    Template expressions

    A template expression allows you to perform code operations inside of ${} we learned about earlier. You can perform addition, subtraction and even call functions inside of interpolation.

    In the following simple example, we are adding two and two together. The value that will be displayed will be 4.

    You can call functions with parameters if you have a function inside your view model.

    You can also use ternaries inside of your interpolation expressions:

    Optional Syntax

    Also supported in template expressions is optional syntax. Aurelia supports the following optional syntax in templates.

    • ??

    • ?.

    • ?.()

    • ?.[]

    While Aurelia supports a few optional syntaxes, ??= is not supported.

    Using optional syntax and nullish coalescing allows us to create safer expressions without the need for if.bind or boilerplate code in view models.

    This can help clean up what otherwise might have been long and complex ternary expressions to achieve the above result.

    Notes on syntax

    You would be forgiven for thinking that you can do pretty much anything that Javascript allows you to do, but there are limitations in what you can do inside of interpolation you need to be aware of.

    1. Expressions cannot be chained using ; or ,

    2. You cannot use primitives such as Boolean, String, instanceOf, typeof and so on

    3. You can only use the pipe separator | when using value converters but not as a bitwise operator

    Attribute Bindings

    Attribute binding in Aurelia allows you to bind to any native HTML attribute in your templates. Binding to HTML attributes in Aurelia allows you to modify classes, style properties, attribute states and more.

    The basic syntax for most attributes being bound is:

    You can bind almost every attribute from this list here. Some examples of what you can bind to can be found below with code examples.

    Binding syntax

    In Aurelia, you can bind attributes in more than one way, and it is important to understand the difference in syntax. To illustrate our point, we are going to be using the native id attribute for our example.

    Binding with interpolation

    You can bind values with interpolation. The following example illustrates how this looks.

    We specify the id attribute and then use string interpolation to get the headingId value. This will populate the id attribute with our value (if one exists).

    Binding with keywords

    For a full list of binding keywords, please see below. However, we are now going to bind the id attribute using the .bind keyword. If the value being bound is null or undefined the attribute will not be displayed.

    This achieves the same result. The value headingId will populate the id attribute and add the value.

    A note on binding. Both approaches detailed above from an implementation perspective are the same. You can use either of the above approaches, and there would be no noticeable difference in performance or features.

    Binding Keywords:

    • one-time: flows data in one direction, from the view model to the view, once.

    • to-view / one-way: flows data in one direction, from the view model to the view.

    • from-view: flows data in one direction, from the view to the view model.

    • two-way: flows data both ways, from view-model to view and from view to view-model.

    • bind: automatically chooses the binding mode. Uses two-way binding for form controls and to-view binding for almost everything else.

    The first input uses the bind command to create two-way bindings for input value attribute bindings automatically. The second and third input uses the two-way / from-view commands which explicitly set the binding modes. For the first and second inputs, their value will be updated whenever the bound view-model firstName / lastName properties are updated, and those properties will also be updated whenever the inputs change.

    For the third input, changes in the bound view-model middleName property will not update the input value. However, changes in the input will update the view model. The first anchor element uses the bind command that automatically creates a to-view binding for anchor HREF attributes. The other two anchor elements use the to-view and one-time commands to explicitly set the binding's mode.

    Binding to images

    You can bind to numerous image properties, but the most common is the src attribute that allows you to bind the image source. The value in the below example is imageSrc which is a property inside of the view model.

    Want to bind to the alt text attribute?

    Disabling buttons and inputs

    You can easily disable a button by binding to the native disabled attribute of buttons and inputs.

    The disableButton value is a class property boolean. When disableButton is true, the button is disabled.

    Binding to innerHtml and textContent

    The native innerhtml and textcontent properties allow you to set the values of HTML elements. When binding to these properties, the difference between what to choose is textcontent will not display HTML tags and innerhtml will.

    Binding values to custom elements

    When working with custom elements in Aurelia, if you leverage bindables to have custom bindable properties allowing values to be bound, you will use .bind extensively.

    Say you had a custom element that accepted an email value. You might call it email inside your component definition.

    Referencing our custom element, if we wanted to bind a value to our email property, we would do this:

    This allows us to pass in data to custom elements cleanly and familiarly.

    Events

    Using Aurelia's intuitive event binding syntax, you can listen to mouse clicks, keyboard events, mouse movements, touches and other native browser events that are accessible via Javascript.

    If you have familiarized yourself with other aspects of Aurelia's template binding, a lot of this will look similar to you. Due to differences in how certain events function (bubbling vs non-bubbling), there are some nuances to be aware of when working with them.

    You can listen to events using two different types of event bindings; trigger and capture. The syntax for event binding is the event name you want to target, followed by one of the above event bindings.

    To listen to an click event on a button, for example, you would do something like this:

    Inside the quotation marks, you specify the function's name to be called inside your view model.

    Common events

    There are several events that you will bind onto in your Aurelia applications. These events are native events that Aurelia can bind to.

    click

    You will listen to the click event on buttons and links using click.trigger

    keypress

    The native keypress event using keypress.trigger will allow you to listen to keypress events on inputs and other elements.

    Capturing event binding

    The capture event binding command should only be used as a last resort. Primarily in situations where an event is fired too early before Aurelia can capture it (third-party plugins, for example) or an event is being stopped using event.preventDefault capture can guarantee the event is captured (hence the name).

    In most situations, trigger will be more than sufficient.

    Template References

    Template references and variables allow you to identify and specify parts of your templates that are accessible both inside of the view itself as well as the view model.

    Using the ref attribute, you can denote elements as variables.

    We can then reference our input by the identifying value we provided to the ref attribute. For inputs, this is convenient because we can access the value property of the input itself.

    You can also access this referenced element inside of your view model as well. Just make sure if you're using TypeScript to specify this property before attempting to reference it inside of your code.

    The ref attribute has several qualifiers you can use in conjunction with custom elements and attributes:

    • view-model.ref="expression": create a reference to a custom element's view-model

    • custom-attribute.ref="expression": create a reference to a custom attribute's view-model

    • controller.ref="expression": create a reference to a custom element's controller instance

    Template references are a great way to reference elements inside view models for use with third-party libraries. They negate the need to query for elements using Javascript APIs.

    Template Variables

    In your view templates, you can specify inline variables using the <let> custom element.

    The <let> element supports working with interpolation strings, plain strings, referencing view model variables and other let bindings within your templates.

    You could then display this value using its camelCase variant:

    You can bind to variable values in a <let> too:

    Template Promises

    When working with promises in Aurelia, previously in version 1, you had to resolve them in your view model and then pass the values to your view templates. It worked, but you had to write code to handle those promise requests. In Aurelia 2, we can work with promises directly inside of our templates.

    The promise.bind template controller allows you to use then, pending and catch in your view removing unnecessary boilerplate.

    A basic example

    The promise binding is intuitive, allowing you to use attributes to bind to steps of the promise resolution process from initialization (pending to resolution and errors).

    Promise binding using functions

    In the following example, notice how we have a parent div with the promise.bind binding and then a method called fetchAdvice? Followed by other attributes inside then.from-view and catch.from-view which handles both the resolved value as well as any errors.

    Ignore the i variable being incremented, this is only there to make Aurelia fire off a call to our fetchAdvice method as it sees the parameter value has changed.

    The parameter i passed to the method fetchAdvice() call in the template is for refreshing binding purposes. It is not used in the method itself. This is because method calls in Aurelia are considered pure, and will only be called again if any of its parameters have changed.

    This example can also be seen in action below.

    Promise bind scope

    The promise template controller creates its own scope. This prevents accidentally polluting the parent scope or the view model where this template controller is used. Let's see an example to understand what it means.

    In the example above, we are storing the resolved value from the promise in the data property, and then passing the value to the foo-bar custom element by binding the foo-data property.

    This is useful when we need the data only in view for passing from one component to another custom element, as it does not pollute the underlying view model. Note that this does not make any difference regarding data binding or change observation. However, when we do need to access the settled data inside the view model, we can use the $parent.data or $parent.err as shown in the example below.

    Nested promise bindings

    If you have a promise inside of a promise (promise-ception), you can nest promise controllers in your markup.

    Using promise bindings inside of a repeat.for

    Due to the way the scoping and binding context resolution works, you might want to use a let binding when using the promise inside repeat.for.

    The above example shows usage involving repeat.for chained with a promisify value converter. The value converter converts a simple value to a resolving or rejecting promise depending on the second boolean value. The value converter in itself is not that important for this discussion. It is used to construct a repeat.for, promise combination easily.

    The important thing to note here is the usage of let binding that forces the creation of two properties, namely data and err, in the overriding context, which gets higher precedence while binding.

    Without these properties in the overriding context, the properties get created in the binding context, which eventually gets overwritten with the second iteration of the repeat. In short, with let binding in place, the output looks as follows.

    template literals
    MDN
    base#href
    external attribute
    default behavior of building the title
    data property
    external attribute
    navigation options
    load custom attribute

    Router hooks

    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.

    Anatomy of a lifecycle hook

    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.

    Example: Authentication and Authorization

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

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

    Global registration vs local dependencies

    The lifecycle hooks can be registered either globally (as it is done in the or as .

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

    Preemption

    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.

    Order of invocations

    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 .

    That is also true, when registering hooks as one of the dependencies for a custom element. You can see this in the example of .

    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 .

    Lastly, the shared lifecycle hooks are invoked before the instance lifecycle hooks.

    import { watch } from '@aurelia/runtime-html';
    
    // on class
    @watch(expressionOrPropertyAccessFn, changeHandlerOrCallback)
    class MyClass {}
    
    // on method
    class MyClass {
      @watch(expressionOrPropertyAccessFn)
      someMethod() {}
    }
    import { watch } from '@aurelia/runtime-html';
    
    class NameComponent {
      name = '';
    
      @watch('name')
      nameChange(newName, oldName) {
        console.log(newName); // The new name value
        console.log(oldName); // The old name value
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class PostOffice {
      packages = [];
    
      @watch('packages.length')
      log(newCount, oldCount) {
        if (newCount > oldCount) {
          // new packages came
        } else {
          // packages delivered
        }
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class PostOffice {
        packages = [];
    
        @watch(post => post.packages.length)
        log(newCount, oldCount) {
            if (newCount > oldCount) {
                // new packages came
            } else {
                // packages delivered
            }
        }
    }
    import { watch } from '@aurelia/runtime-html';
    
    @watch('counter', (newValue, oldValue, app) => app.log(newValue))
    class App {
    
      counter = 0;
    
      log(whatToLog) {
        console.log(whatToLog);
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    @watch('counter', 'log')
    class App {
    
      counter = 0;
    
      log(whatToLog) {
        console.log(whatToLog);
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    @watch('counter', function(newValue, oldValue, app) {
      app.log(newValue);
      // or use this, it will point to the instance of this class
      this.log(newValue);
    })
    class App {
    
      counter = 0;
    
      log(whatToLog) {
        console.log(whatToLog);
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    @watch(function (app) { return app.counter }, (newValue, oldValue, app) => app.log(newValue))
    class App {
    
      counter = 0;
    
      log(whatToLog) {
        console.log(whatToLog);
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    @watch(app => app.counter, (newValue, oldValue, app) => app.log(newValue))
    class App {
    
      counter = 0;
    
      log(whatToLog) {
        console.log(whatToLog);
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class App {
      counter = 0;
    
      @watch('counter')
      log(whatToLog) {
        console.log(whatToLog);
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class App {
      counter = 0;
    
      @watch(function(app) { return app.counter })
      log(whatToLog) {
        console.log(whatToLog);
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class App {
      counter = 0;
    
      @watch(app => app.counter)
      log(whatToLog) {
        console.log(whatToLog);
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class PostOffice {
      packages = [];
    
      @watch(post => post.packages.length)
      log(newCount, oldCount) {
        console.log(`packages changes: ${oldCount} -> ${newCount}`);
      }
    
      // lifecycle
      binding() {
        this.packages.push({ id: 1, name: 'xmas toy', delivered: false });
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class PostOffice {
      packages = [];
    
      @watch(post => post.packages.length)
      log(newCount, oldCount) {
        console.log(`packages changes: ${oldCount} -> ${newCount}`);
      }
    
      // lifecycle
      bound() {
        this.packages.push({ id: 1, name: 'xmas toy', delivered: false });
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class PostOffice {
      packages = [];
    
      @watch(post => post.packages.length)
      log(newCount, oldCount) {
        console.log(`packages changes: ${oldCount} -> ${newCount}`);
      }
    
      // lifecycle
      detaching() {
        this.packages.push({ id: 1, name: 'xmas toy', delivered: false });
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class PostOffice {
      packages = [];
    
      @watch(post => post.packages.length)
      log(newCount, oldCount) {
        console.log(`packages changes: ${oldCount} -> ${newCount}`);
      }
    
      // lifecycle
      unbinding() {
        this.packages.push({ id: 1, name: 'xmas toy', delivered: false });
      }
    }
    // don't do this
    @watch(object => object.counter++)
    someMethod() {}
    
    // don't do these
    @watch(object => object.someArray.push(...args))
    @watch(object => object.someArray.pop())
    @watch(object => object.someArray.shift())
    @watch(object => object.someArray.unshift())
    @watch(object => object.someArray.splice(...args))
    @watch(object => object.someArray.reverse())
    someMethod() {}
    import { watch } from '@aurelia/runtime-html';
    
    const defaultOptions = {};
    
    class MyClass {
      options = defaultOptions;
    
      @watch(myClass => myClass.options === defaultOptions ? null : myClass.options)
      applyCustomOptions() {
        // ...
      }
    }
    import { watch } from '@aurelia/runtime-html';
    
    class MyClass {
      // don't do this
      @watch(async myClassInstance => myClassinstance.options)
      applyCustomOptions() {}
    
      // don't do this
      @watch(myClassInstance => {
        Promise.resolve().then(() => {
          return myClassinstance.options
        })
      })
    }
    <let i.bind="0"></let>
    
    <div promise.bind="fetchAdvice(i)">
      <span pending>Fetching advice...</span>
      <span then.from-view="data">
        Advice id: ${data.slip.id}<br>
        ${data.slip.advice}
        <button click.trigger="i = i+1">try again</button>
      </span>
      <span catch.from-view="err">
        Cannot get an advice, error: ${err}
        <button click.trigger="i = i+1">try again</button>
      </span>
    </div>
    export class MyApp {
      fetchAdvice() {
        return fetch(
            "https://api.adviceslip.com/advice",
            {
              // This is not directly related to promise template controller.
              // This is simply to ensure that the example demonstrates the
              // change in data in every browser, without any confusion.
              cache: 'no-store'
            }
          )
          .then(r => r.ok
            ? r.json()
            : (() => { throw new Error('Unable to fetch NASA APOD data') })
          )
      }
    }
    my-app.ts
    export class MyApp {
      myName = 'Aurelia';
    }
    my-app.html
    <p>Hello, my name is ${myName}</p>
    <p>Quick maths: ${2 + 2}</p>
    my-app.ts
    export class MyApp {
      adder(val1, val2) {
        return parseInt(val1) + parseInt(val2);
      }
    }
    my-app.html
    <p>Behold mathematics, 6 + 1 = ${adder(6, 1)}</p>
    <p>${isTrue ? 'True' : 'False'}</p>
    ${myValue ?? 'Some default'}
    <div attribute-name.bind="value"></div>
    <div>
        <h1 id="${headingId}">My Heading</h1>
    </div>
    <div>
        <h1 id.bind="headingId">My Heading</h1>
    </div>
      <input type="text" value.bind="firstName">
      <input type="text" value.two-way="lastName">
      <input type="text" value.from-view="middleName">
    
      <a class="external-link" href.bind="profile.blogUrl">Blog</a>
      <a class="external-link" href.to-view="profile.twitterUrl">Twitter</a>
      <a class="external-link" href.one-time="profile.linkedInUrl">LinkedIn</a>
    <img src.bind="imageSrc">
    <img src.bind="imageSrc" alt.bind="altValue">
    my-component.html
    <button disabled.bind="disableButton">Disabled Button</button>
    my-component.ts
    export class MyComponent {
        disableButton = true;
    }
    my-component.html
    <div textcontent.bind="myContent"></div>
    my-component.html
    <div innerhtml.bind="myContent"></div>
    my-custom-element.ts
    import { bindable, customElement } from 'aurelia';
    
    @customElement('my-custom-element')
    export class MyCustomElement {
      @bindable email = '';
    }
    <my-custom-element email.bind="myEmail"></my-custom-element>
    <button click.trigger="myClickFunction()">Click me!</button>
    <input type="text" ref="myInput" placeholder="First name">
    <p>${myInput.value}</p>
    export class MyApp {
      private myInput: HTMLInputElement;
    }
    <let some-var="This is a string value"></let>
    <p>${someVar}</p>
    <let math-equation.bind="1 + 2 + 5"></let>
    
    <!-- This will display 8 -->
    <p>${mathEquation}</p>
    <div promise.bind="promise1">
     <template pending>The promise is not yet settled.</template>
     <template then.from-view="data">The promise is resolved with ${data}.</template>         <!-- grab the resolved value -->
     <template catch.from-view="err">This promise is rejected with ${err.message}.</template> <!-- grab the rejection reason -->
    </div>
    
    <div promise.bind="promise2">
     <template pending>The promise is not yet settled.</template>
     <template then>The promise is resolved.</template>
     <template catch>This promise is rejected.</template>
    </div
    <div promise.bind="promise">
     <foo-bar then.from-view="data" foo-data.bind="data"></foo-bar>
     <fizz-buzz catch.from-view="err" fizz-err.bind="err"></fizz-buzz>
    </div>
    <template promise.bind="fetchPromise">
     <template pending>Fetching...</template>
     <template then.from-view="response" promise.bind="response.json()">
       <template then.from-view="data">${data}</template>
       <template catch>Deserialization error</template>
     </template>
     <template catch.from-view="err2">Cannot fetch</template>
    </template>
    <let items.bind="[[42, true], ['foo-bar', false], ['forty-two', true], ['fizz-bazz', false]]"></let>
    <template repeat.for="item of items">
      <template promise.bind="item[0] | promisify:item[1]">
        <let data.bind="null" err.bind="null"></let>
        <span then.from-view="data">${data}</span>
        <span catch.from-view="err">${err.message}</span>
      </template>
    </template>
    import { valueConverter } from '@aurelia/runtime-html';
    
    @valueConverter('promisify')
    class Promisify {
      public toView(value: unknown, resolve: boolean = true): Promise<unknown> {
        return resolve
          ? Promise.resolve(value)
          : Promise.reject(new Error(String(value)));
      }
    }
    <span>42</span>
    <span>foo-bar</span>
    <span>forty-two</span>
    <span>fizz-bazz</span>
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router-lite';
    
    Aurelia
      .register(RouterConfiguration.customize({ useUrlFragmentHash: true }))
      .app(component)
      .start();
    <head>
      <base href="/">
    </head>
    <!-- app1/index.html -->
    <head>
      <base href="/app1">
    </head>
    
    <!-- app2/index.html -->
    <head>
      <base href="/app2">
    </head>
      // this can either be '/', '/app[/+]', or '/TENANT_NAME/app[/+]'
      let basePath = location.pathname;
      const tenant =
        (!basePath.startsWith('/app') && basePath != '/'
          ? basePath.split('/')[1]
          : null) ?? 'none';
      if (tenant === 'none') {
        basePath = '/app';
      }
      const host = document.querySelector<HTMLElement>('app');
      const au = new Aurelia();
      au.register(
        StandardConfiguration,
        RouterConfiguration.customize({
          basePath,
        }),
        Registration.instance(ITenant, tenant) // <-- this is just to inject the tenant name in the `my-app.ts`
      );
    tenant: ${tenant}
    <nav>
      <a href="${baseUrl}/foo/app" external>Switch to tenant foo</a>
      <a href="${baseUrl}/bar/app" external>Switch to tenant bar</a>
    </nav>
    <nav>
      <a load="home">Home</a>
      <a load="about">About</a>
    </nav>
    
    <au-viewport></au-viewport>
    
    import { customElement } from '@aurelia/runtime-html';
    import { route } from '@aurelia/router-lite';
    import template from './my-app.html';
    import { Home } from './home';
    import { About } from './about';
    import { DI } from '@aurelia/kernel';
    
    export const ITenant = DI.createInterface<string>('tenant');
    
    @route({
      routes: [
        {
          path: ['', 'home'],
          component: Home,
          title: 'Home',
        },
        {
          path: 'about',
          component: About,
          title: 'About',
        },
      ],
    })
    @customElement({ name: 'my-app', template })
    export class MyApp {
      private baseUrl = location.origin;
      public constructor(@ITenant private readonly tenant: string) {}
    }
    import { route, IRouteViewModel } from '@aurelia/router-lite';
    @route({
        title: 'Aurelia', // <-- this is the base title
        routes: [
          {
            path: ['', 'home'],
            component: import('./components/home-page'),
            title: 'Home',
          }
        ]
    })
    export class MyApp implements IRouteViewModel {}
    // main.ts
    import { RouterConfiguration, Transition } from '@aurelia/router';
    import { Aurelia } from '@aurelia/runtime-html';
    const au = new Aurelia();
    au.register(
      RouterConfiguration.customize({
        buildTitle(tr: Transition) {
          const root = tr.routeTree.root;
          const baseTitle = root.context.config.title;
          const titlePart = root.children.map(c => c.title).join(' - ');
          return `${baseTitle} - ${titlePart}`;
        },
      }),
    );
    import { IRouteViewModel, Routeable } from "aurelia";
    export class MyApp implements IRouteViewModel {
      static title: string = 'Aurelia';
      static routes: Routeable[] = [
        {
          path: ['', 'home'],
          component: import('./components/home-page'),
          title: 'Home',
          data: {
            i18n: 'routes.home'
          }
        }
      ];
    }
    import { I18N, Signals } from '@aurelia/i18n';
    import { IEventAggregator } from '@aurelia/kernel';
    import { IRouter, RouterConfiguration, Transition } from '@aurelia/router';
    import { AppTask, Aurelia } from '@aurelia/runtime-html';
    (async function () {
      const host = document.querySelector<HTMLElement>('app');
      const au = new Aurelia();
      const container = au.container;
      let i18n: I18N | null = null;
      let router: IRouter | null = null;
      au.register(
        // other registrations such as the StandardRegistration, I18NRegistrations come here
        RouterConfiguration.customize({
          buildTitle(tr: Transition) {
            // Use the I18N to translate the titles using the keys from data.i18n.
            i18n ??= container.get(I18N);
            const root = tr.routeTree.root;
            const baseTitle = root.context.config.title;
            const child = tr.routeTree.root.children[0];
            return `${baseTitle} - ${i18n.tr(child.data.i18n as string)}`;
          },
        }),
        AppTask.afterActivate(IEventAggregator, ea => {
          // Ensure that the title changes whenever the locale is changed.
          ea.subscribe(Signals.I18N_EA_CHANNEL, () => {
            (router ??= container.get(IRouter)).updateTitle();
          });
        }),
      );
      // start aurelia here
    })().catch(console.error);
    {
      "routes": {
        "home": "Startseite"
      }
    }
    <a href="mailto:[email protected]">Email Me</a>
    <a href="mailto:[email protected]" external>Email Me</a>
    import Aurelia from 'aurelia';
    import { RouterConfiguration } from '@aurelia/router-lite';
    
    Aurelia
      .register(RouterConfiguration.customize({
          useHref: false
      }))
      .app(component)
      .start();
    import { RouterConfiguration } from '@aurelia/router-lite';
    import { Aurelia, StandardConfiguration } from '@aurelia/runtime-html';
    import { MyApp as component } from './my-app';
    
    (async function () {
      const host = document.querySelector<HTMLElement>('app');
      const au = new Aurelia();
      au.register(
        StandardConfiguration,
        RouterConfiguration.customize({
          historyStrategy: 'push', // default value can can be omitted
        })
      );
      au.app({ host, component });
      await au.start();
    })().catch(console.error);
    import { IHistory } from '@aurelia/runtime-html';
    import { IRouterEvents } from '@aurelia/router-lite';
    
    export class MyApp {
      private history: string;
      public constructor(
        @IHistory history: IHistory,
        @IRouterEvents events: IRouterEvents
      ) {
        let i = 0;
        events.subscribe('au:router:navigation-end', () => {
          this.history = `#${++i} - len: ${history.length} - state: ${JSON.stringify(history.state)}`;
        });
      }
    }
    interface IWatcher {
        observeProperty(obj: object, key: string | number | symbol): void;
        observeCollection(collection: Array | Map | Set): void;
    }
    import { watch } from '@aurelia/runtime-html';
    
    class Contact {
      firstName = 'Chorris';
      lastName = 'Nuck';
    
      @watch((contact, watcher) => {
        watcher.observeProperty(contact, 'firstName');
        watcher.observeProperty(contact, 'lastName');
        return `${contact.firstName} ${contact.lastName}`;
      })
      validateFullName(fullName) {
        if (fullName === 'Chuck Norris') {
          this.faint();
        }
      }
    }
    data property
    section
    previous example
    local dependencies
    dependencies
    globally registered hooks
    hooks as dependencies
    this example
    router-lite - canLoad - true/false - StackBlitzStackBlitz
    router-lite - canLoad - routeid - nav instruction - StackBlitzStackBlitz
    router-lite - unloading - StackBlitzStackBlitz
    router-lite - canLoad - sibling nav instructions - StackBlitzStackBlitz
    router-lite - canLoad - string nav instruction - StackBlitzStackBlitz
    router-lite - loading - StackBlitzStackBlitz
    router-lite - canUnload - StackBlitzStackBlitz
    router-lite - loading - title - StackBlitzStackBlitz
    import {
      IRouteViewModel,
      Params,
      RouteNode,
      NavigationInstruction,
    } from '@aurelia/router-lite';
    
    export class MyComponent implements IRouteViewModel {
      canLoad?(
        params: Params,
        next: RouteNode,
        current: RouteNode | null
      ): boolean
        | NavigationInstruction
        | NavigationInstruction[]
        | Promise<boolean | NavigationInstruction | NavigationInstruction[]>;
      loading?(
        params: Params,
        next: RouteNode,
        current: RouteNode | null
      ): void | Promise<void>;
      canUnload?(
        next: RouteNode | null,
        current: RouteNode
      ): boolean | Promise<boolean>;
      unloading?(
        next: RouteNode | null,
        current: RouteNode
      ): void | Promise<void>;
    }
    import { lifecycleHooks } from 'aurelia';
    import {
      IRouteViewModel,
      Params,
      RouteNode,
      NavigationInstruction
    } from '@aurelia/router-lite';
    
    @lifecycleHooks()
    class MySharedHooks {
      canLoad?(
        viewModel: IRouteViewModel,
        params: Params,
        next: RouteNode,
        current: RouteNode | null
      ): boolean
        | NavigationInstruction
        | NavigationInstruction[]
        | Promise<boolean | NavigationInstruction | NavigationInstruction[]>;
      loading?(
        viewModel: IRouteViewModel,
        params: Params,
        next: RouteNode,
        current: RouteNode | null
      ): void | Promise<void>;
      canUnload?(
        viewModel: IRouteViewModel,
        next: RouteNode | null,
        current: RouteNode
      ): boolean | Promise<boolean>;
      unloading?(
        viewModel: IRouteViewModel,
        next: RouteNode | null,
        current: RouteNode
      ): void | Promise<void>;
    }
    my-app.ts
    import { route } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    import { Login } from './login';
    import { Forbidden } from './forbidden';
    import { Restricted } from './restricted';
    
    @route({
      routes: [
        { path: '', redirectTo: 'home' },
        {
          path: 'home',
          component: Home,
        },
        {
          path: 'login',
          component: Login,
        },
        {
          path: 'forbidden',
          component: Forbidden,
        },
        {
          path: 'about',
          component: About,
          data: {
            claim: { type: 'read', resource: 'foo' },
          },
        },
        {
          path: 'restricted',
          component: Restricted,
          data: {
            claim: { type: 'manage', resource: 'foo' },
          },
        },
      ],
    })
    export class MyApp {}
    authentication-hook.ts
    import {
      IRouteViewModel,
      NavigationInstruction,
      Params,
      RouteNode,
    } from '@aurelia/router-lite';
    import { lifecycleHooks } from '@aurelia/runtime-html';
    import { IAuthenticationService } from './authentication-service';
    
    @lifecycleHooks()
    export class AuthenticationHook {
      public constructor(
        @IAuthenticationService private readonly authService: IAuthenticationService
      ) {}
      public canLoad(
        _viewmodel: IRouteViewModel,
        _params: Params,
        next: RouteNode
      ): boolean | NavigationInstruction {
        if (!next.data?.claim || this.authService.currentClaims != null)
          return true;
        // we add the current url to the return_url query to the login page,
        // so that login page can redirect to that url after successful login.
        return `login?return_url=${next.computeAbsolutePath()}`;
      }
    }
    authorization-hook.ts
    import {
      IRouteViewModel,
      NavigationInstruction,
      Params,
      RouteNode,
    } from '@aurelia/router-lite';
    import { lifecycleHooks } from '@aurelia/runtime-html';
    import { Claim } from './authentication-service';
    import { IAuthenticationService } from './authentication-service';
    
    @lifecycleHooks()
    export class AuthorizationHook {
      public constructor(
        @IAuthenticationService private readonly authService: IAuthenticationService
      ) {}
      public canLoad(
        _viewmodel: IRouteViewModel,
        _params: Params,
        next: RouteNode
      ): boolean | NavigationInstruction {
        const claim = next.data?.claim as Claim;
        if (!claim) return true;
        if (this.authService.hasClaim(claim.type, claim.resource)) return true;
        return 'forbidden';
      }
    }
    main.ts
    import { RouterConfiguration } from '@aurelia/router-lite';
    import { Aurelia, StandardConfiguration } from '@aurelia/runtime-html';
    import { AuthenticationHook } from './authentication-hook';
    import { IAuthenticationService } from './authentication-service';
    import { AuthorizationHook } from './authorization-hook';
    import { MyApp as component } from './my-app';
    
    (async function () {
      const host = document.querySelector<HTMLElement>('app');
      const au = new Aurelia();
      au.register(
        StandardConfiguration,
        RouterConfiguration,
        IAuthenticationService,
    
        // register the first lifecycle hook
        AuthenticationHook,
        // register the second lifecycle hook
        AuthorizationHook
      );
      au.app({ host, component });
      await au.start();
    })().catch(console.error);
    import { ILogger } from '@aurelia/kernel';
    import { IRouteViewModel, Params, RouteNode } from '@aurelia/router-lite';
    import { lifecycleHooks } from '@aurelia/runtime-html';
    
    @lifecycleHooks()
    export class Hook1 {
      public static readonly scope = 'hook1';
      private readonly logger: ILogger;
      public constructor(@ILogger logger: ILogger) {
        this.logger = logger.scopeTo(Hook1.scope);
      }
      public canLoad(_vm: IRouteViewModel, _params: Params, next: RouteNode): boolean {
        this.logger.debug(`canLoad '${next.computeAbsolutePath()}'`);
        return true;
      }
      public loading(_vm: IRouteViewModel, _params: Params, next: RouteNode) {
        this.logger.debug(`loading '${next.computeAbsolutePath()}'`);
      }
      public canUnload(_vm: IRouteViewModel, _next: RouteNode, current: RouteNode): boolean {
        this.logger.debug(`canUnload '${current.computeAbsolutePath()}'`);
        return true;
      }
      public unloading(_vm: IRouteViewModel, _next: RouteNode, current: RouteNode) {
        this.logger.debug(`unloading '${current.computeAbsolutePath()}'`);
      }
    }
    
    @lifecycleHooks()
    export class Hook2 {
      public static readonly scope = 'hook2';
      private readonly logger: ILogger;
      public constructor(@ILogger logger: ILogger) {
        this.logger = logger.scopeTo(Hook2.scope);
      }
      public canLoad(_vm: IRouteViewModel, _params: Params, next: RouteNode): boolean {
        this.logger.debug(`canLoad '${next.computeAbsolutePath()}'`);
        return true;
      }
      public loading(_vm: IRouteViewModel, _params: Params, next: RouteNode) {
        this.logger.debug(`loading '${next.computeAbsolutePath()}'`);
      }
      public canUnload(_vm: IRouteViewModel, _next: RouteNode, current: RouteNode): boolean {
        this.logger.debug(`canUnload '${current.computeAbsolutePath()}'`);
        return true;
      }
      public unloading(_vm: IRouteViewModel, _next: RouteNode, current: RouteNode) {
        this.logger.debug(`unloading '${current.computeAbsolutePath()}'`);
      }
    }
    2023-01-29T20:03:23.885Z [DBG hook1] canLoad ''
    2023-01-29T20:03:23.887Z [DBG hook2] canLoad ''
    2023-01-29T20:03:23.888Z [DBG hook1] loading ''
    2023-01-29T20:03:23.888Z [DBG hook2] loading ''
    2023-01-29T20:10:09.403Z [DBG hook1] canUnload ''
    2023-01-29T20:10:09.407Z [DBG hook2] canUnload ''
    2023-01-29T20:10:09.410Z [DBG hook1] canLoad 'c1/42'
    2023-01-29T20:10:09.410Z [DBG hook2] canLoad 'c1/42'
    2023-01-29T20:10:09.410Z [DBG hook1] unloading ''
    2023-01-29T20:10:09.411Z [DBG hook2] unloading ''
    2023-01-29T20:10:09.411Z [DBG hook1] loading 'c1/42'
    2023-01-29T20:10:09.411Z [DBG hook2] loading 'c1/42'
    // child1.ts
    import { customElement } from '@aurelia/runtime-html';
    import { Hook1, Hook3 } from './hooks';
    
    @customElement({
      dependencies: [Hook1, Hook3],
    })
    export class ChildOne {}
    
    // child2.ts
    import { customElement } from '@aurelia/runtime-html';
    import { Hook2 } from './hooks';
    
    @customElement({
      dependencies: [Hook2],
    })
    export class ChildTwo {}
    2023-02-01T21:59:23.525Z [DBG hook2] canLoad 'c2/43'
    2023-02-01T21:59:23.527Z [DBG hook2] loading 'c2/43'
    2023-02-01T21:59:25.353Z [DBG hook2] canUnload 'c2/43'
    2023-02-01T21:59:25.355Z [DBG hook1] canLoad 'c1/42'
    2023-02-01T21:59:25.355Z [DBG hook3] canLoad 'c1/42'
    2023-02-01T21:59:25.356Z [DBG hook2] unloading 'c2/43'
    2023-02-01T21:59:25.356Z [DBG hook1] loading 'c1/42'
    2023-02-01T21:59:25.357Z [DBG hook3] loading 'c1/42'
    2023-02-02T19:12:51.503Z [DBG hook1] canLoad 'c1/42'
    2023-02-02T19:12:51.505Z [DBG hook2] canLoad 'c1/42'
    2023-02-02T19:12:51.506Z [DBG hook1] loading 'c1/42'
    2023-02-02T19:12:51.506Z [DBG hook2] loading 'c1/42'
    2023-02-02T19:12:55.287Z [DBG hook1] canUnload 'c1/42'
    2023-02-02T19:12:55.288Z [DBG hook2] canUnload 'c1/42'
    2023-02-02T19:12:55.288Z [DBG hook1] canLoad 'c1/43'
    Logo
    Logo
    Logo
    Logo
    Logo
    router-lite - canLoad - accessing fragment and query - StackBlitzStackBlitz
    Logo
    Logo
    Logo
    router-lite - translate title - StackBlitzStackBlitz
    router-lite - historyStrategy - none - StackBlitzStackBlitz
    router-lite - buildTitle - StackBlitzStackBlitz
    router-lite - base[href] w/o configuration - StackBlitzStackBlitz
    router-lite - basePath - StackBlitzStackBlitz

    Viewports

    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 ), is the "outlet", where the router-lite attaches/loads the components. For a basic example of viewport, please refer the . 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.

    Hierarchical routing

    As seen in the , a component can define a set of children routes (using

    Testing

    Learn how to write unit and end-to-end tests for Aurelia applications. Strategies for mocking, component instantiation and more are detailed in this comprehensive guide.

    Testing is an integral part of modern development, and Aurelia supports testing through helper methods and ways of instantiating the framework in a test environment. Aurelia supports numerous test runners, including Jest and Mocha, and the guiding test principles are the same.

    When it comes to testing, Aurelia provides a testing package @aurelia/testing comes with some helpers functions, including a fixture creation method that allows you to instantiate components and handle setup and teardown.

    When you test components and other view resources in Aurelia, you will write integration tests and query the DOM for changes to content. Not quite a unit test because we are testing behaviors of our code in the view. However, writing both integration and unit tests is highly recommended.

    Logo
    Unit vs integration vs e2e tests

    If you are new to testing or inexperienced, it is worth noting that when dealing with tests in Aurelia, they are broken down into three distinct categories.

    • Unit tests — Testing units of your code independently (if statements, function calls, throws, etc.). Most commonly, a unit test involves testing the code itself.

    • Integration tests — An integration test is an evolutionary leap on unit tests. Instead of testing lines of code in isolation, you test them as a whole. In Aurelia, an integration test commonly refers to staging a resource (component, attribute, value converter) and testing it to have the desired outcome in the UI.

    • End-to-end tests (E2E) — An e2e test allows you to test behavior in the browser. Think of test cases involving logging into your application and being redirected to a dashboard screen, a button triggering a popup. These are things you would test for in an e2e test.

    In the Aurelia documentation, we will not be covering e2e tests as those are outside the realm of the framework itself. Although, we do highly recommend Cypress for end-to-end testing.

    Configuring the test environment

    Because tests can be run in various environments, you need to set this part up before running tests. Setting up the environment requires configuring the platform using the setPlatform method.

    This initialization code can be placed in a shared file that all your tests load, or it can be added to each test. The best approach to handle this is to create a function that you call to set this all up.

    You can then call this function at the beginning of each new test to specify the environment for the tests to run.

    A basic test

    For a basic test example, we'll use the au-compose element and test that our view string is rendered into the page. Using the createFixture method, we'll get back a few different properties we can use to determine the test status and content to query.

    The createFixture method is being used here (specifying HTML to render), but it can do much more. You can pass in your view model as the second argument (a class with properties), and for the third argument, pass in resource dependencies such as components, value converters, etc.

    Testing components

    You already saw how to test a component in the basic setup section. In our example, we tested the au-compose element, but in most instances, you will be testing your components to ensure they render properly and handle data.

    To showcase how we can test components and components with bindable properties, we will create a fictitious example.

    Now, we'll create a view for our custom element:

    Writing the test

    We want to test that our custom element says what it should say when data is passed into it.

    Testing custom attributes

    A custom attribute is like a component, except it doesn't have a view template. Using the same testing techniques we learned in the components section, we can also easily test our custom attributes.

    Before we write any tests, let's create a custom attribute that adds a color border to the element used. We created this attribute in the Custom Attributes section. A simple but effective attribute that will have a predictable outcome we can test for.

    Our color square attribute will make an element uniform in size (same height and width) and allow a color value to be set.

    Our basic test confirms that our custom attribute modifies the width (default value 100px). We query for this element using its ID, but we could also target it using the custom attribute. The appHost is the dom our test is being instrumented in. We query for our element using its ID and then assert the width property on the style object.

    You might notice how we instrument our test similarly to how we do it for components. The first argument of createFixture is our HTML view, the second is our view model where we can define values to bind in our view model, and the third is where we can specify dependencies (custom elements, value converters, components) being used.

    Testing value converters

    If you have tried writing tests for components and attributes, value converters will once again feel familiar to you. Where value converters differ is you will write both unit and integration tests (in the same file).

    Let's start by creating a value converter we can test, something that takes a string and then transforms it to uppercase.

    Our value converter checks if the value is valid, and if it is, it returns the value in uppercase. Otherwise, the value provided is returned.

    Now, onto our test. Our test is going to test the code itself, as well as being used inside of a view template with different types of values.

    With the first three tests, we instantiated the value converter directly and did not run it through the Aurelia pipeline. It's just a class with a method that we call with values. Notice how we provide an invalid value, a valid value and a value containing a mixture of numbers and strings? We are covering all of our bases and seeing how our value converter handles mixed input.

    Good tests test all different outcomes. A successful outcome, an unsuccessful outcome and unknown outcomes.

    This approach can be used for many class-based codes you might have. You don't have to use the fixture bootstrap functionality to test code, it's great for testing component output and behaviors, but for code unit tests, it is not needed.

    How to mock, stub and spy on DI dependencies

    One of the advantages of using dependency injection is how easy it makes mocking DI dependencies. Some people have strong opinions on mocks, but in Aurelia, they can make your life stress-free when testing your apps. A mock allows you to replace complicated code, such as server calls, with code that works the same way but doesn't make real calls.

    When you mock a dependency, you're replacing the real version with a fake stub or hijacking calls and writing the return functionality on the fly. There is no right or wrong way to mock dependencies.

    Mocks, spies and stubs using Sinon

    Sinon is a powerful and well-known library for adding mock, stub and spy functionality to your tests. For more complex tests, a library like Sinon will make testing a lot more enjoyable and prevent the need to reimplement the same functionality.

    Install Sinon and types:

    If you are not using TypeScript, you can omit the @types/sinon however, if you are using TypeScript, keep it, so you get intellisense when writing tests and referencing the Sinon package and its methods.

    If you are not using TypeScript, you won't need to install @types/sinon

    Inside your tests, you import the Sinon package and reference it inside of your test cases.

    Before we do that, let's create a basic component with an injected dependency and see how we can mock it.

    This simplistic component injects the router and has a method called navigate which loads a route when called. It's a contrived example because you probably wouldn't do this in a real application, but it allows us to see how we can mock and stub inside tests.

    Stubbing individual methods

    In this test, we will use a Sinon stub to stub out the load method inside the router instance. This saves us from having to go and completely implement the router in mock form. We only care about one function here.

    Mocking an entire dependency

    Sometimes, you will want to replace a dependency with a fake version completely. Maybe the Fetch API or something else primarily does things external to your code. For that, you can mock the entire dependency itself.

    When our component requests the dependency IRouter , our registered instance provided to the third argument createFixture will be the value it gets instead of the default one. Because our mocked load method is doing what our stub did, the result is the same.

    We use the Registration.instance method to register our mock router as an instance of IRouter which all parts of our application we are testing will use. For more information on how this works, consult the Dependency Injection section to learn more.

    Spying on methods

    Kind of like a stub, a spy allows you to observe methods and determine when they are called. Maybe your component has a button with a callback function that gets triggered when the button is pressed. You don't want to reimplement the callback, but you want to make sure it gets called.

    Where spies are useful is not only in knowing when a method is called but how many times it was called and what parameters it was supplied.

    Now, let's create our test for our magic button. We want to spy on the save method inside of our component, so we use sinon.spy to create a spy. We can then determine if the method is called or not. We know our callbackFunction method calls it, so we call that.

    Mocks via the constructor

    Sometimes you want to mock dependencies directly on the constructor itself. This approach means you will manually instantiate your classes and not stage them like you would integration tests using createFixture.

    You would use this approach when taking a more traditional unit test approach to testing.

    We'll take the same custom element from the above section:

    Inside our test file, you'll notice things are a little cleaner than in the previous examples. This is because we don't stage the component anymore. We instantiate it ourselves. We lose the ability to query the HTML, but it allows us to test the code in a more traditional sense (which you might prefer).

    As you can see in our test, we create an object that we provide in place of the router that would usually be injected. We are making the router load method return whatever is provided to it. We want to make sure the load method is called.

    This approach does mean you have to stub and mock the entire implementation, but it can be convenient for situations where you only want to test a couple of methods.

    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { setPlatform } from '@aurelia/testing';
    
    const platform = new BrowserPlatform(window);
    setPlatform(platform);
    BrowserPlatform.set(globalThis, platform);
    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { setPlatform } from '@aurelia/testing';
    
    function bootstrapTestEnvironment() {
        const platform = new BrowserPlatform(window);
        setPlatform(platform);
        BrowserPlatform.set(globalThis, platform);
    }
    import { assert, createFixture } from '@aurelia/testing';
    
    // An assumption is being made you called the code defined in the first part
    // of these docs to set up the environment.
    
    describe('My basic test', function() {
        it('should pass test', async function() {
          const { appHost, startPromise, tearDown } = createFixture(
            '<au-compose template="<div>hello world</div>">'
          );
    
          await startPromise;
          assert.strictEqual(appHost.textContent, 'hello world');
    
          await tearDown();
    
          assert.strictEqual(appHost.textContent, '');
        });
    });
    person-detail.ts
    import { bindable } from 'aurelia';
    
    export class PersonDetail {
        @bindable name;
        @bindable age;
    }
    person-detail.html
    <p>Person is called ${name} and is ${age} years old.</p>
    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { assert, createFixture, setPlatform } from '@aurelia/testing';
    
    import { PersonDetail } from './person-detail';
    
    const platform = new BrowserPlatform(window);
    setPlatform(platform);
    BrowserPlatform.set(globalThis, platform);
    
    describe('Person Detail', function() {
        it('should render when passed name and age', async function() {
          const { appHost, startPromise, tearDown } = createFixture(
            '<person-detail name.bind="personName" age.bind="personAge"></person-detail>',
            class App {
                personName = 'Rob';
                personAge = 29;
            },
            [ PersonDetail ]
          );
    
          await startPromise;
    
          assert.strictEqual(appHost.textContent, 'Person is called Rob and is 29 years old.');
    
          await tearDown();
    
          assert.strictEqual(appHost.textContent, '');
        });
    })
    color-square.ts
      import { bindable, customAttribute, INode } from 'aurelia';
    
      @customAttribute('color-square')
      export class ColorSquareCustomAttribute {
        @bindable() color: string = 'red';
        @bindable() size: string = '100px';
    
        constructor(@INode private element: HTMLElement){
            this.element.style.width = this.element.style.height = this.size;
            this.element.style.backgroundColor = this.color;
        }
    
        bound() {
          this.element.style.width = this.element.style.height = this.size;
          this.element.style.backgroundColor = this.color;
        }
    
        colorChanged(newColor, oldColor) {
          this.element.style.backgroundColor = newColor;
        }
    
        sizeChanged(newSize: string, oldSize: string) {
          this.element.style.width = this.element.style.height = newSize;
        }
    }
    color-square.spec.ts
    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { assert, createFixture, setPlatform } from '@aurelia/testing';
    
    import { ColorSquareCustomAttribute } from './color-square';
    
    const platform = new BrowserPlatform(window);
    setPlatform(platform);
    BrowserPlatform.set(globalThis, platform);
    
    describe('Color Square', function() {
        it('works with default values', async function() {
          const { appHost, startPromise, tearDown } = createFixture(
            '<div id="attributeel" color-square></div>',
            class App {},
            [ ColorSquareCustomAttribute ]
          );
    
          await startPromise;
    
          const el = appHost.querySelector('#attributeel') as HTMLElement;
    
          assert.strictEqual(el.style.width, '100px');
    
          await tearDown();
        });
    });
    to-uppercase.ts
    import { valueConverter } from 'aurelia';
    
    @valueConverter('toUpper')
    export class ToUpper {
        toView(str) {
            if (!str) {
                return str;
            }
    
            return str.toUpperCase();
        }
    }
    to-uppercase.spec.ts
    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { assert, createFixture, setPlatform } from '@aurelia/testing';
    
    import { ToUpper } from './to-upper';
    
    const platform = new BrowserPlatform(window);
    setPlatform(platform);
    BrowserPlatform.set(globalThis, platform);
    
    describe('To Upper', function() {
        // Passing in null should return null
        it('returns invalid values', function() {
            const sut = new ToUpper();
    
            assert.strictEqual(sut.toView(null), null);
        });
    
        // Passing in a string should return in uppercase
        it('returns provided string as uppercase', function() {
            const sut = new ToUpper();
    
            assert.strictEqual(sut.toView('rOb wAs hErE'), 'ROB WAS HERE');
        });
    
        // Passing in a string containing numbers should return only string in uppercase
        it('returns provided string as uppercase', function() {
            const sut = new ToUpper();
    
            assert.strictEqual(sut.toView('rOb wAs hErE'), 'ROB WAS HERE');
        });
    
        it('works in a view', async function() {
          const { appHost, startPromise, tearDown } = createFixture(
            '<div>${text | toUpper}</div>',
            class App {
                text = 'rob is here 123';
            },
            [ ToUpper ]
          );
    
          await startPromise;
    
          assert.strictEqual(appHost.textContent, 'ROB IS HERE 123');
    
          await tearDown();
    
          assert.strictEqual(appHost.textContent, '');
        });
    });
    npm install sinon @types/sinon -D
    my-component.ts
    import { IRouter } from '@aurelia/router';
    import { customElement } from 'aurelia';
    
    @customElement('my-component')
    export class MyComponent {
        constructor(@IRouter private router: IRouter) {
    
        }
    
        navigate(path) {
            return this.router.load(path);
        }
    }
    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { assert, createFixture, setPlatform } from '@aurelia/testing';
    import { IRouter, RouterConfiguration } from '@aurelia/router';
    
    import { MyComponent } from '../src/components/my-component';
    
    import sinon from 'sinon';
    
    const platform = new BrowserPlatform(window);
    setPlatform(platform);
    BrowserPlatform.set(globalThis, platform);
    
    describe('Component Test', function() {
    
        it('should mock dependencies', async function() {
            const { startPromise, appHost, tearDown, component, ctx, container } = createFixture(
                `<my-component></my-component>`,
                MyComponent,
                [ RouterConfiguration ]
              );
    
              await startPromise;
    
              // The router property is private, so get the router instance
              // from the container
              const router = container.get(IRouter);
    
              // Stub load and return first argument
              sinon.stub(router, 'load').returnsArg(0);
    
              assert.strictEqual(component.navigate('nowhere'), 'nowhere');
    
              await tearDown();
        });
    });
    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { assert, createFixture, setPlatform } from '@aurelia/testing';
    import { IRouter, RouterConfiguration } from '@aurelia/router';
    
    import { MyComponent } from '../src/components/my-component';
    
    import sinon from 'sinon';
    
    const platform = new BrowserPlatform(window);
    setPlatform(platform);
    BrowserPlatform.set(globalThis, platform);
    
    const mockRouter = {
        load(path) {
            return path;
        }
    };
    
    describe('Component Test', function() {
    
        it('should mock dependencies', async function() {
            const { startPromise, appHost, tearDown, component, ctx, container } = createFixture(
                `<my-component></my-component>`,
                MyComponent,
                [ Registration.instance(IRouter, mockRouter) ]
              );
    
              await startPromise;
    
              assert.strictEqual(component.navigate('nowhere'), 'nowhere');
    
              await tearDown();
        });
    });
    magic-button.ts
    import { customElement } from 'aurelia';
    
    @customElement('magic-button')
    export class MagicButton {
        callbackFunction(event, id) {
            return this.save(event, id);
        }
    
        save(event, id) {
            // This would call the API or something...
            return `${id}__special`;
        }
    }
    magic-button.spec.ts
    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { assert, createFixture, setPlatform } from '@aurelia/testing';
    
    import { MagicButton } from '../src/components/magic-button';
    
    import sinon from 'sinon';
    
    const platform = new BrowserPlatform(window);
    setPlatform(platform);
    BrowserPlatform.set(globalThis, platform);
    
    describe('Magic Button', function() {
    
        it('magic button calls save function', async function() {
            const { startPromise, appHost, tearDown, component, ctx, container } = createFixture(
                `<magic-button></magic-button>`,
                MagicButton
              );
    
              await startPromise;
    
              const save = sinon.spy(component, 'save');
    
              component.callbackFunction(new CustomEvent('test'), 2);
    
              save.restore();
              sinon.assert.calledOnce(save);
    
              await tearDown();
        });
    });
    my-element.ts
    import { customElement } from 'aurelia';
    
    @customElement('my-component')
    export class MyComponent {
        constructor(@IRouter private router: IRouter) {
    
        }
    
        navigate(path) {
            return this.router.load(path);
        }
    }
    my-element.spec.ts
    import { BrowserPlatform } from '@aurelia/platform-browser';
    import { assert, setPlatform } from '@aurelia/testing';
    import { IRouter, RouterConfiguration } from '@aurelia/router';
    
    import { MyComponent } from '../src/components/my-component';
    
    const platform = new BrowserPlatform(window);
    setPlatform(platform);
    BrowserPlatform.set(globalThis, platform);
    
    describe('Component Test', function() {
    
        // We need any or TypeScript will complain it's not a proper router instance
        const mockRouter: any = {
            load(path) {
                return path;
            }
        };
    
        it('should mock dependencies', function() {
            const sut = new MyElement(mockRouter);
    
            assert.strictEqual(sut.navigate('nowhere'), 'nowhere');
        });
    });
    ). 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.

    Sibling viewports

    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.

    Named viewports

    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.

    Using viewport name for routing instructions

    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.

    Specifying a viewport name on a route

    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 anchors in the example that show that the viewport names can now be dropped from the routing instructions.

    Reserve viewports for components using 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.

    Specify a default component for a viewport

    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.

    Specify a fallback component for a viewport

    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.

    viewport meta tag
    "Getting started"-tutorial
    "Getting started"-tutorial
    either the @route decorator or the static properties
    Logo
    Logo
    Logo
    router-lite - historyStrategy - push - StackBlitzStackBlitz
    Logo
    Logo
    router-lite - historyStrategy - replace - StackBlitzStackBlitz
    Au2 Promise binding using functions - StackBlitzStackBlitz

    Bindable properties

    How to create components that accept one or more bindable properties. You might know these as "props" if you are coming from other frameworks and libraries.

    When creating components, sometimes you will want the ability for data to be passed into them. The @bindable decorator allows you to specify one or more bindable properties for a component.

    The @bindable attribute also can be used with custom attributes as well as custom elements. The decorator denotes bindable properties on components on the view model of a component.

    This will allow our component to be passed in values. Our specified bindable property here is called loading and can be used like this:

    In the example above, we are binding the boolean literal true to the loading property.

    Scope and context

    Understand the scope and binding context.

    You might have noticed the words "Scope", "binding context", and "override context" in other places in the documentation or while working with Aurelia in general. Although you can go a long way without even understanding what these are (Aurelia is cool that way), these are some (of many) powerful concepts that are essential when dealing with the lower-level Aurelia 2 API. This section explains what these terms mean.

    Here's what you'll learn...

    import { customElement } from '@aurelia/runtime-html';
    import { route } from '@aurelia/router-lite';
    import template from './my-app.html';
    import { Products } from './products';
    
    @route({
      routes: [
        { path: '', redirectTo: 'products' },
        {
          path: 'products',
          component: Products,
        },
      ],
    })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
    <nav>
      <a load="products">Products</a>
    </nav>
    
    <au-viewport></au-viewport> <!-- the root viewport -->
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    import { Product } from './product';
    import { IProductService, ProductDetail } from './product-service';
    import template from './products.html';
    
    // child route configuration
    @route({
      routes: [
        {
          id: 'product',
          path: ':id/details',
          component: Product,
        },
      ],
    })
    @customElement({ name: 'pro-ducts', template })
    export class Products {
      promise: Promise<ProductDetail[]>;
      public constructor(@IProductService productService: IProductService) {
        this.promise = productService.getAll();
      }
    }
    <style>
      div.content {
        display: flex;
        gap: 1rem;
        padding: 1rem;
      }
    </style>
    
    <div class="content">
      <div promise.bind="promise">
        <span pending>Fetching products...</span>
        <div then.bind="data">
          Fetched ${data.length} products
          <ul>
            <li repeat.for="item of data">
              <a href="${item.id}/details">${item.title}</a>
            </li>
          </ul>
        </div>
      </div>
    
      <au-viewport></au-viewport> <!-- the child viewport -->
    </div>
    +--------------------------------------------------------------------+
    |                                                                    |
    |   Root-Viewport                                                    |
    |   +                                                                |
    |   |                                                                |
    |   |  +---------------------------------------------------------+   |
    |   +->+                                                         |   |
    |      |   Products                                              |   |
    |      |                                                         |   |
    |      |   +-------------+     Child-Viewport                    |   |
    |      |                       +                                 |   |
    |      |   +-------------+     |                                 |   |
    |      |                       |  +--------------------------+   |   |
    |      |   +-------------+     +->+                          |   |   |
    |      |                          |    Product details       |   |   |
    |      |   +-------------+        |                          |   |   |
    |      |                          |                          |   |   |
    |      |   +-------------+        |                          |   |   |
    |      |                          |                          |   |   |
    |      |   +-------------+        |                          |   |   |
    |      |                          |                          |   |   |
    |      |   +-------------+        |                          |   |   |
    |      |                          +--------------------------+   |   |
    |      +---------------------------------------------------------+   |
    +--------------------------------------------------------------------+
    
    +--------------------------------------------------------------------+
    |                                                                    |
    |   Viewport#1                      Viewport#2                       |
    |   +                               +                                |
    |   |                               |                                |
    |   |  +------------------------+   |  +------------------------+    |
    |   +->+                        |   +->+                        |    |
    |      |   Products' List       |      |    Product details     |    |
    |      |                        |      |                        |    |
    |      |                        |      |                        |    |
    |      |   +-------------+      |      |                        |    |
    |      |                        |      |                        |    |
    |      |   +-------------+      |      |                        |    |
    |      |                        |      |                        |    |
    |      |   +-------------+      |      |                        |    |
    |      |                        |      |                        |    |
    |      |   +-------------+      |      |                        |    |
    |      |                        |      |                        |    |
    |      +------------------------+      +------------------------+    |
    +--------------------------------------------------------------------+
    my-app.ts
    import { customElement } from '@aurelia/runtime-html';
    import { route } from '@aurelia/router-lite';
    import template from './my-app.html';
    import { Products } from './products';
    import { Product } from './product';
    
    @route({
      routes: [
        {
          id: 'products',
          path: ['', 'products'],
          component: Products,
        },
        {
          id: 'details',
          path: 'details/:id',
          component: Product,
        },
      ],
    })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
    my-app.html
    <style>
      div.content {
        display: flex;
        gap: 1rem;
        padding: 1rem;
      }
    </style>
    
    <nav>
      <a load="products">Products</a>
    </nav>
    
    <div class="content">
      <au-viewport></au-viewport>
      <au-viewport></au-viewport>
    </div>
    my-app.html
      <div class="content">
    -   <au-viewport></au-viewport>
    -   <au-viewport></au-viewport>
    +   <!-- instruct the router to load the products component by default -->
    +   <au-viewport default="products"></au-viewport>
    +   <au-viewport default.bind="null"></au-viewport>
      </div>
    my-app.html
      <div class="content">
    -   <au-viewport></au-viewport>
    -   <au-viewport default.bind="null"></au-viewport>
    +   <au-viewport name="list" default="products"></au-viewport>
    +   <au-viewport name="details" default.bind="null"></au-viewport>
      </div>
    products.html
      <ul>
        <li repeat.for="item of data">
    -     <a href="#">${item.title}</a>
    +     <a load="route.bind:{component:'details', params: {id: item.id}, viewport:'details'}; context.bind:null">${item.title}</a>
        </li>
      </ul>
    <main>
        <au-viewport name="main"></au-viewport>
    </main>
    <aside>
        <au-viewport name="sidebar"></au-viewport>
    </aside>
    {path}@{viewport-name}
    <a load="products@list+details/${id}@details">Load products@list+details/${id}@details</a>
    <a load="details/${id}@details">Load details/${id}@details</a>
    import { route } from '@aurelia/router-lite';
    import { Products } from './products';
    import { Product } from './product';
    
    @route({
      routes: [
        {
          id: 'products',
          path: 'products',
          component: Products,
          viewport: 'list',
        },
        {
          id: 'details',
          path: 'details/:id',
          component: Product,
          viewport: 'details',
        },
      ],
    })
    export class MyApp {}
    <nav>
      <!-- clicking this will load the products into the 'list' viewport -->
      <a load="products">products</a>
      <!-- clicking this will load the products into the 'list' viewport and the details of product #3 into the 'details' viewport -->
      <a load="products+details/3">products+details/3</a>
      <!-- same as above; but shows that the sibling order does not matter -->
      <a load="details/4+products">details/4+products</a>
    </nav>
    <au-viewport used-by="ce-two"></au-viewport>
    <au-viewport used-by="ce-one"></au-viewport>
    <au-viewport used-by="ce-one,ce-two"></au-viewport>
    <au-viewport used-by="ce-one"></au-viewport>
    <div class="content">
    
      <!-- loads the empty route -->
      <au-viewport></au-viewport>
    
      <!-- loads the ce-two with parameter -->
      <au-viewport default="foo/42"></au-viewport>
    
      <!-- loads the ce-one -->
      <au-viewport default="ce-one"></au-viewport>
    
      <!-- loads the ce-two without parameter -->
      <au-viewport default="foo"></au-viewport>
    
    </div>
    import { customElement } from '@aurelia/runtime-html';
    import {
      IRouteContext,
      ITypedNavigationInstruction_string,
      route,
      RouteNode,
      ViewportInstruction,
    } from '@aurelia/router-lite';
    import template from './my-app.html';
    
    @customElement({ name: 'ce-a', template: 'a' })
    class A {}
    
    @customElement({ name: 'n-f-1', template: 'nf1' })
    class NF1 {}
    
    @customElement({ name: 'n-f-2', template: 'nf2' })
    class NF2 {}
    
    @route({
      routes: [
        { id: 'r1', path: ['', 'a'], component: A },
        { id: 'r2', path: ['nf1'], component: NF1 },
        { id: 'r3', path: ['nf2'], component: NF2 },
      ],
    })
    @customElement({
      name: 'my-app',
      template: `<nav>
      <a href="a">A</a>
      <a href="foo">Foo</a>
      <a href="bar">Bar</a>
    </nav>
    
    <au-viewport fallback.bind></au-viewport>`
    })
    export class MyApp {
      fallback(vi: ViewportInstruction, _rn: RouteNode, _ctx: IRouteContext): string {
        return (vi.component as ITypedNavigationInstruction_string).value === 'foo' ? 'r2' : 'r3';
      }
    }
    Logo
    Logo
    Logo
    Instead of literal, you can also bind another property (loadingVal in the following example) to the loading property.

    As seen in the following example, you can also bind values without the loading.bind part.

    Aurelia treats attribute values as strings. This means when working with primitives such as booleans or numbers, they won't come through in that way and need to be coerced into their primitive type using a bindable setter or specifying the bindable type explicitly using bindable coercion.

    The @bindable decorator signals to Aurelia that a property is bindable in our custom element. Let's create a custom element where we define two bindable properties.

    import { bindable } from 'aurelia'; 
    
    export class NameComponent {
        @bindable firstName = '';
        @bindable lastName  = '';
    }
    <p>Hello ${firstName} ${lastName}. How are you today?</p>

    You can then use the component in this way,`<name-component first-name="John" last-name="Smith"></name-component>

    Calling a change function when bindable is modified

    By default, Aurelia will call a change callback (if it exists) which takes the bindable property name followed by Changed added to the end. For example, firstNameChanged(newVal, previousVal) would fire every time the firstName bindable property is changed.

    Due to the way the Aurelia binding system works, change callbacks will not be fired upon initial component initialization. If you worked with Aurelia 1, this behavior differs from what you might expect.

    If you would like to call your change handler functions when the component is initially bound (like v1), you can achieve this the following way:

    Configuring bindable properties

    Like almost everything in Aurelia, you can configure how bindable properties work.

    Change the binding mode using mode

    You can specify the binding mode using the mode property and passing in a valid BindingMode to it; @bindable({ mode: BindingMode.twoWay}) - this determines which way changes flow in your binding. By default, this will be BindingMode.oneWay

    Please consult the binding modes documentation below to learn how to change the binding modes. By default, the binding mode for bindable properties will be one-way

    Change the name of the change callback

    You can change the name of the callback that is fired when a change is made @bindable({ callback: 'propChanged' })

    Bindable properties support many different binding modes determining the direction the data is bound in and how it is bound.

    One way binding

    By default, bindable properties will be one-way binding. This means values flow into your component but not back out of it (hence the name, one way).

    Bindable properties without an mode explicitly set will be one-way by default. You can also explicitly specify the binding mode.

    Two-way binding

    Unlike the default, the two-way binding mode allows data to flow in both directions. If the value is changed with your component, it flows back out.

    Working with two-way binding

    Much like most facets of binding in Aurelia, two-way binding is intuitive. Instead of .bind you use .two-way if you need to be explicit, but in most instances, you will specify the type of binding relationship a bindable property is using with @bindable instead.

    Explicit two-way binding looks like this:

    The myVal variable will get a new value whenever the text input is updated. Similarly, if myVal were updated from within the view model, the input would get the updated value.

    When using .bind for input/form control values such as text inputs, select dropdowns and other form elements. Aurelia will automatically create a two-way binding relationship. So, the above example using a text input can be rewritten to be value.bind="myVal" , and it would still be a two-way binding.

    Bindable setter

    In some cases, you want to make an impact on the value that is binding. For such a scenario, you can use the possibility of new set.

    Suppose you have a carousel component in which you want to enable navigator feature for it.

    In version two, you can easily implement such a capability with the set feature.

    To make things easier, first design a new type that accepts true and false as a string and a boolean.

    Define your property like this:

    For set part, we need functionality to check the input. If the value is one of the following, we want to return true, otherwise, we return the false value.

    • '': No input for a standalone navigator property.

    • true: When the navigator property set to true.

    • "true": When the navigator property set to "true".

    So our function will be like this

    Now, we should set truthyDetector function as follows:

    Although, there is another way to write the functionality too:

    You can simply use any of the above four methods to enable/disable your feature. As you can see, set can be used to transform the values being bound into your bindable property and offer more predictable results when dealing with primitives like booleans and numbers.

    Bindable coercion

    The bindable setter section shows how to adapt the value is bound to a @bindable property. One common usage of the setter is to coerce the values that are bound from the view. Consider the following example.

    Without any setter for the @bindable num we will end up with the string '42' as the value for num in MyEl. You can write a setter to coerce the value. However, it is a bit annoying to write setters for every @bindable.

    Automatic type coercion

    To address this issue, Aurelia 2 supports type coercion. To maintain backward compatibility, automatic type coercion is disabled by default and must be enabled explicitly.

    There are two relevant configuration options.

    enableCoercion

    The default value is false; that is Aurelia 2 does not coerce the types of the @bindable by default. It can be set to true to enable the automatic type-coercion.

    coerceNullish

    The default value is false; that is Aurelia2 does not coerce the null and undefined values. It can be set to true to coerce the null and undefined values as well. This property can be thought of as the global counterpart of the nullable property in the bindable definition (see Coercing nullable values section).

    Additionally, depending on whether you are using TypeScript or JavaScript for your app, there can be several ways to use automatic type coercion.

    For TypeScript development

    For TypeScript development, this gets easier when the emitDecoratorMetadata configuration property in tsconfig.json is set to true. When this property is set, and the @bindable properties are annotated with types, there is no need to do anything else; Aurelia 2 will do the rest.

    If, for some reason, you cannot do that, then refer to the next section.

    For JavaScript development

    For JavaScript development, you need to specify the explicit type in the @bindable definition.

    The rest of the document is based on TypeScript examples. However, we trust that you can transfer that knowledge to your JavaScript codebase if necessary.

    Coercing primitive types

    Currently, coercing four primitive types are supported out of the box. These are number, string, boolean, and bigint. The coercion functions for these types are respectively Number(value), String(value), Boolean(value), and BigInt(value).

    Be mindful when dealing with bigint as the BigInt(value) will throw if the value cannot be converted to bigint; for example null, undefined, or non-numeric string literal.

    Coercing to instances of classes

    It is also possible to coerce values into instances of classes. There are two ways how that can be done.

    Using a static coerce method

    You can define a static method named coerce in the class used as a @bindable type. This method will be called by Aurelia2 automatically to coerce the bound value.

    This is shown in the following example with the Person class.

    According to the Person#coercer implementation, for the example above MyEl#person will be assigned an instance of Person that is equivalent to new Person('john', null).

    Using the @coercer decorator

    Aurelia2 also offers a @coercer decorator to declare a static method in the class as the coercer. The previous example can be rewritten as follows using the @coercer decorator.

    With the @coercer decorator, you are free to name the static method as you like.

    Coercing nullable values

    To maintain backward compatibility, Aurelia2 does not attempt to coerce null and undefined values. We believe that this default choice should avoid unnecessary surprises and code breaks when migrating to newer versions of Aurelia.

    However, you can explicitly mark a @bindable to be not nullable.

    When nullable is set to false, Aurelia2 will try to coerce the null and undefined values.

    set and auto-coercion

    It is important to note that an explicit set (see bindable setter) function is always prioritized over the type. In fact, the auto-coercion is the fallback for the set function. Hence whenever set is defined, the auto-coercion becomes non-operational.

    However, this gives you an opportunity to:

    • Override any of the default primitive type coercing behavior, or

    • Disable coercion selectively for a few selective @bindable by using a noop function for set.

    Aurelia2 already exposes a noop function saving your effort to write such boring functions.

    Union types

    When using TypeScript, usages of union types are not rare. However, using union types for @bindable will deactivate the auto-coercion.

    For the example above, the type metadata supplied by TypeScript will be Object disabling the auto-coercion.

    To coerce union types, you can explicitly specify a type.

    However, using a setter would be more straightforward to this end.

    Even though using a noop function for set function is a straightforward choice, Object can also be used for type in the bindable definition to disable the auto-coercion for selective @bindables (that is when the automatic type-coercion is enabled).

    Attributes Transferring

    Attribute transferring is a way to relay the binding(s) on a custom element to its child element(s).

    As an application grows, the components inside it also grow. Something that starts simple, like the following component

    with the template

    can quickly grow out of hand with a number of needs for configuration: aria, type, min, max, pattern, tooltip, validation etc...

    After a while, the FormInput component above will become more and more like a relayer to transfer the bindings from outside, to the elements inside it. This often results in an increase in the number of @bindable. While this is fine, you end up with components that have a lot of boilerplate.

    And the usage of our component would look like this:

    to be repeated like this inside:

    To juggle all the relevant pieces for such a task isn't difficult, but somewhat tedious. With attribute transferring, which is roughly close to object spreading in JavaScript, the above template should be as simple as:

    , which reads like this: for some bindings on <form-input>, change the targets of those bindings to the <input> element inside it.

    Usage

    To transfer attributes & bindings from a custom element, there are two steps:

    • Set capture to true on a custom element via @customElement decorator:

    Or use the capture decorator from aurelia package if you don't want to declare the customElement decorator and have to specify your name and template values.

    As the name suggests, this is to signal the template compiler that all the bindings & attributes, with some exceptions, should be captured for future usage.

    Spread the captured attributes onto an element

    Using the ellipsis syntax which you might be accustomed to from Javascript, we can spread our attributes onto an element proceeding the magic variable $attrs

    Spread attributes and overriding specific ones

    In case you want to spread all attributes while explicitly overriding individual ones, make sure these come after the spread operator.

    It's recommended that this feature should not be overused in multi-level capturing & transferring. This is often known as prop-drilling in React and could have a bad effect on the overall & long-term maintainability of an application. It's probably healthy to limit the max level of transferring to 2.

    Usage with conventions

    Aurelia conventions enable the setting of capture metadata from the template via <capture> tag, like the following example:

    Attribute filtering

    Sometimes it is desirable to capture only certain attributes on a custom element. Aurelia supports this via 2nd form of the custom element capture value: a function that takes 1 parameter, which is the attribute name, and returns a boolean to indicate whether it should be captured.

    How it works

    What attributes are captured

    Everything except the template controller and custom element bindables are captured.

    A usage example is as follows:

    What is captured:

    • value.bind="extraComment"

    • class="form-control"

    • style="background: var(--theme-purple)"

    • tooltip="Hello, ${tooltip}"

    What is not captured:

    • if.bind="needsComment" (if is a template controller)

    • label.bind="label" (label is a bindable property)

    How will attributes be applied in ...$attrs

    Attributes that are spread onto an element will be compiled as if it was declared on that element.

    This means .bind command will work as expected when it's transferred from some element onto some element that uses .two-way for .bind.

    It also means that spreading onto a custom element will also work: if a captured attribute targets a bindable property of the applied custom element. An example:

    if value is a bindable property of my-input, the end result will be a binding that connects the message property of the corresponding app.html view model with <my-input> view model value property. The binding mode is also preserved like normal attributes.

    What is Scope?
  • What is binding context and override context?

  • How to troubleshoot the rare and weird data binding issues?

  • How is a context selected?

  • Background

    When we start an Aurelia app, the compilation pipeline JIT compiles the templates (HTML/markup) associated with custom elements. The compilation process demands documentation of its own and is out of this topic's scope. Without going into much detail about that, we can think of the compilation process in terms of the following steps:

    • Parse the template text,

    • Create instructions for custom elements, custom attributes, and template controllers (if, else, repeat.for etc.), and

    • Create a set of bindings for every instruction.

    Most of the bindings also contain expressions.

    In the example above, the interpolation binding has the expression firsName, and the property binding has the expression address.pin (quite unsurprisingly, things are a bit more involved in actuality, but this abstraction will do for now).

    An expression in itself might not be that interesting, but when it is evaluated, it becomes of interest. Enter scope. To evaluate an expression, we need a scope.

    Scope and binding context

    The expressions themselves do not hold any state or context. This means that the expression firstName only knows that given an object, it needs to grab the firstName property of that object. However, the expression, in itself, does not hold that object. The scope is the container that holds the object(s) which can be supplied to the expression when it is evaluated.

    These objects are known as contexts. There are typically two types of contexts: binding context and override context. An expression can be evaluated against any of these two kinds of contexts. Even though there are a few subtle differences between these two kinds of contexts (see Override context), in terms of expression evaluation, there is no difference between these two.

    JavaScript analogy

    One way to think about expression and binding context is in terms of functions and binding those functions with an execution context (Refer: Function.bind).

    Let us consider the following example.

    If we invoke this function like foo(), we will get NaN. However, binding any object to it might return a more meaningful value, depending on the bound object.

    Following that analogy, the expressions are like this function, or more precisely, like the expression a ** 2 in the function. Binding contexts are like the objects used to bind the function. That is, given 2 different binding contexts, the same expression can produce different results when evaluated. Scope, as said before, wraps the binding context, almost like the scope in JavaScript. The need to have this wrapper over the binding context is explained in later sections.

    How to access the scope and the binding context?

    Aurelia pipeline injects a $controller property to every custom element, custom attribute, and template controller. This property can be used to access the scope and binding context.

    Let us consider the following example.

    Note that we haven't assigned any value explicitly to the $controller property, and the Aurelia pipeline assigns that. We can use the $controller.scope to access the scope and subsequently $controller.scope.bindingContext can be used to access the binding context.

    Note how the bindingContext in the above example points to this, that is the current instance of App (with template controllers, this gets a little more involved; but we will leave that one out for now). However, we refer to the data source as a "context" in evaluating expressions.

    The relations explored so far can be expressed as follows.

    From here, let us proceed to understand what override context is.

    Override context

    As the name suggests, it is also a context that overrides the binding context. Aurelia gives higher precedence to the overriding context when the desired property is found there. This means that while binding if a property is found in both binding and override context, the latter will be selected to evaluate the expression.

    We continue with the previous example; it renders <div>Hello World!</div>. However, things might be a bit different if we toy with the overriding context, as shown in the following example.

    The assignment to overrideContext.message the rendered output is now <div>Hello Aurelia!</div> , instead of <div>Hello World!</div>. This is because of the existence of the property message in the overriding context.

    As the assignment is made pre-binding phase (created hook in the example above), the context selection process sees that the required property exists in the overriding context and selects that with higher precedence even though a property with the same name also exists in the binding context.

    Now with this information, we also have a new diagram.

    Motivation

    Now let's address the question 'Why do we need override context at all?'. The reason it exists has to do with the template controllers (mostly). While writing template controllers, many times we want a context object that is not the underlying view-model instance. One such prominent example is the repeat.for template controller.

    As you might know that repeat.for template controller provides contextual properties such as $index, $first, $last etc. These properties end up being in the override context.

    Now imagine if those properties actually end up being in the binding context, which is often the underlying view-model instance. It would have caused a lot of other issues. First, that would have restricted you from having properties with the same name to avoid conflicts.

    This, in turn, means that you need to know the template controllers you are using thoroughly to know about such restrictions, which is not a sound idea in itself. And with that, if you define a property with the same name, as used by the template controller, coupled with change observation etc., we could have found ourselves dealing with numerous bugs in the process. Override context helps us to get out of that horrific mess.

    Another prominent use caseend for override context is the let binding. When not specified otherwise, the properties bound via the let binding ends up in the overriding context.

    This can be seen in the example below.

    Typically the properties for the let-bindings are view-only properties. It makes sense to have those properties in the overriding context.

    Do you know that you can use to-binding-context attribute in let-binding to target the binding context instead of override context? Why don't you try <let foo.bind="42" to-binding-context></let> and inspect the scope contexts by yourself?

    Parent scope

    The discussion so far has explained the necessity of context. However, that still does not answer the question, 'If the expressions are evaluated based on the context, why do we even need scope?'. Apart from serving as a logical container for the contexts, a scope also optionally points to the parent scope.

    Let us consider the following example to understand that.

    The example above App uses the FooBar custom element, and both have property named message, initialized with different values. As expected, the rendered output in this case is Hello Foo-Bar! Hello App!.

    You might have used the $parent keyword a lot, but for completeness, it should be clarified that the parent scope can be accessed using the $parent keyword. The example above FooBar#$controller.scope.parentScope.bindingContext points to the instance of App where <foo-bar> is used. In short, every scope instance has a parentScope property that points to the parent scope when available.

    With this information, our diagram changes one last time.

    Note that the parentScope for the scope of the root component is null.

    Host scope

    As we are talking about scope, it needs to be noted that the term 'host scope' is used in the context of au-slot. There is no difference between a "normal" scope and a host scope; it just acts as the special marker to instruct the scope selection process to use the scope of the host element instead of the scope of the parent element.

    Moreover, this is a special kind of scope that is valid only in the context of au-slot. This is already discussed in detail in the au-slot documentation, and thus not repeated here.

    Context and change observation

    Now let us discuss change observation. A comprehensive discussion on change observation is a bit out of this documentation's scope. However, for this discussion, it would suffice to say that generally, whenever Aurelia binds an expression to the view, it employs one or more observers.

    This is how when the value of the underlying property changes, the change is also propagated to view or other associated components. The focus of this discussion is how some interesting scenarios occur in conjunction with binding/override context and the change observation.

    Let's start with a simple example.

    The example above updates the message property of the binding context every 1 second. As Aurelia is also observing the property, the interpolated output is also updated after every 1 second. Note that as the scope.bindingContext above points to the this, updating this.message that way has the same effect.

    As the next example, we change the property in both the binding context and the override context.

    Although it has been said before that the property in override context takes precedence over binding context, the output from the example above is Hello Binding Context! #i: 1, Hello Binding Context! #i: 2, and so on. The reason for this behavior is that the scope.bindingContext.message is bound to the view instead of scope.overrideContext.message, as the latter was non-existent during the binding phase (note that the values are being changed in attached lifecycle hook).

    Therefore, the change observation is also applied for the scope.bindingContext.message as opposed to that of override context. This explains why updating the scope.overrideContext.message is rather 'futile' in the example above.

    However, the result would have been quite different, if the message property is introduced to override context during the binding phase (or before that, for that matter).

    Note that the example above introduces the message property in the overriding context during the binding phase. When the interpolation expression is evaluated in the view, it is that property from the overriding context that ends up being bound. This means that the message property in the overriding context is also observed.

    Thus, quite expectedly, every 1-second output of the above-shown example changes as Hello Override Context! #i: 1, Hello Override Context! #i: 2, and so on.

    Context selection

    So far, we have seen various aspects of scope, binding and override context. One thing we have not addressed so far is how the contexts are selected for expression evaluation or assignment. In this section, we will look into that aspect.

    The context selection process can be summed up (simplified) as follows.

    1. IF $parent keyword is used once or more than once, THEN

      1. traverse up the scope, the required number of parents (that is, for $parent.$parent.foo, we will go two steps/scopes up)

      2. RETURN override context if the desired property is found there, ELSE RETURN binding context.

    2. ELSE

      1. LOOP till either the desired property is found in the context or the component boundary is hit. Then perform the following.

      2. IF the desired property is found in the overriding context, return the override context.

      3. ELSE RETURN binding context.

    The first rule involving $parent should be self-explanatory. We will focus on the second part.

    Let us first see an example to demonstrate the utility of the rule #2.1..

    As expected, the example produces the following output.

    Note that both App and FooBar initializes their own message properties. According to our rule #2.3. binding context is selected, and the corresponding message property is bound to the view. However, it is important to note that if the FooBar#message stays uninitialized, that is the message property exists neither in binding context nor in override context (of FooBar's scope), the output would have been as follows.

    Although it should be quite as per expectation, the point to be noted here is that the scope traversal never reaches to App in the process. This is because of the 'component boundary' clause in rule #2.1.. In case of this example, the expression evaluation starts with the scope of the innermost repeat.for, and traversed upwards.

    When traversal hits the scope of FooBar, it recognize the scope as a component boundary and stops traversing any further, irrespective of whether the property is found or not. Contextually note that if you want to cross the component boundary, you need to explicitly use $parent keyword.

    The rule #2.2. is also self-explanatory, as we have seen plenty of examples of overriding context precedence so far. Thus the last bit of this story boils down to the rule #2.3.. This rule facilitates using an uninitialized property in binding context by default or as a fallback, as can be seen in the example below.

    The example shown above produces Hello World! as output after 2 seconds of the invocation of the attached hook. This happens because of the fallback to binding context by the rule #2.3..

    That's it! Congratulations! You have made it till the end. Go have that tea break now! Hope you have enjoyed this documentation as much as you will enjoy that tea. Have fun with Aurelia2!

    import { bindable } from 'aurelia'; 
    
    export class NameComponent {
        @bindable({ mode: BindingMode.twoWay}) firstName = '';
        @bindable({ callback: 'lnameChanged' }) lastName  = '';
        
        lnameChanged(val) {}
    }
    @customElement({ name:'my-el', template: 'not important' })
    export class MyEl {
      @bindable public num: number;
    }
    @customElement({ name:'my-app', template: '<my-el num="42"></my-el>' })
    export class MyApp { }
    export class Person {
      public constructor(
        public readonly name: string,
        public readonly age: number,
      ) { }
      public static coerce(value: unknown): Person {
        if (value instanceof Person) return value;
        if (typeof value === 'string') {
          try {
            const json = JSON.parse(value) as Person;
            return new this(json.name, json.age);
          } catch {
            return new this(value, null!);
          }
        }
        if (typeof value === 'number') {
          return new this(null!, value);
        }
        if (typeof value === 'object' && value != null) {
          return new this((value as any).name, (value as any).age);
        }
        return new this(null!, null!);
      }
    }
    import { Person } from './person.ts';
    @customElement({ name:'my-el', template: 'not important' })
    export class MyEl {
      @bindable public person: Person;
    }
    @customElement({ name:'my-app', template: '<my-el person="john"></my-el>' })
    export class MyApp { }
    import { coercer } from '@aurelia/runtime-html';
    
    export class Person {
      public constructor(
        public readonly name: string,
        public readonly age: number,
      ) { }
    
      @coercer
      public static createFrom(value: unknown): Person {
        if (value instanceof Person) return value;
        if (typeof value === 'string') {
          try {
            const json = JSON.parse(value) as Person;
            return new this(json.name, json.age);
          } catch {
            return new this(value, null!);
          }
        }
        if (typeof value === 'number') {
          return new this(null!, value);
        }
        if (typeof value === 'object' && value != null) {
          return new this((value as any).name, (value as any).age);
        }
        return new this(null!, null!);
      }
    }
    import { Person } from './person.ts';
    
    @customElement({ name:'my-el', template: 'not important' })
    export class MyEl {
      @bindable public person: Person;
    }
    @customElement({ name:'my-app', template: '<my-el person="john"></my-el>' })
    export class MyApp { }
    loader-component.ts
    import { bindable } from 'aurelia';
    
    export class LoaderComponent {
        @bindable loading = false;
    }
    loader-component.html
    <loader loading.bind="true"></loader>
    loader-component.html
    <loader loading.bind="loadingVal"></loader>
    <loader loading="true"></loader>
    import { bindable } from 'aurelia'; 
    
    export class NameComponent {
        @bindable firstName = '';
        @bindable lastName  = '';
        
        bound() {
            this.firstNameChanged(this.firstName, undefined);
        }
        
        firstNameChanged(newVal, oldVal) {
            console.log('Value changed');
        }
    }
    import { bindable, BindingMode } from 'aurelia';
    
    export class Loader {
        @bindable({ mode: BindingMode.oneWay })
    }
    import { bindable, BindingMode } from 'aurelia';
    
    export class Loader {
        @bindable({ mode: BindingMode.twoWay})
    }
    <input type="text" value.two-way="myVal">
    @bindable({ 
        set: value => function(value),  /* HERE */
        // Or set: value => value,
        mode: /* ... */ 
    })
    <!-- Enable -->
    <my-carousel navigator.bind="true">
    <my-carousel navigator="true">
    <my-carousel navigator=true>
    <my-carousel navigator>
    
    <!-- Disable -->
    <my-carousel navigator.bind="false">
    <my-carousel navigator="false">
    <my-carousel navigator=false>
    <my-carousel>
    export type BooleanString = "true" | "false" | true | false /* boolean */;
    @bindable({ set: /* ? */, mode: BindingMode.toView }) public navigator: BooleanString = false;
    export function truthyDetector(value: unknown) {
        return value === '' || value === true || value === "true";
    }
    @bindable({ set: truthyDetector, mode: BindingMode.toView }) public navigator: BooleanString = false;
    @bindable({ set: v => v === '' || v === true || v === "true", mode: BindingMode.toView }) public navigator: BooleanString = false;
    new Aurelia()
        .register(
          StandardConfiguration
            .customize((config) => {
              config.coercingOptions.enableCoercion = true;
              // config.coercingOptions.coerceNullish = true;
            }),
          ...
        );
    @customElement({ name:'my-el', template: 'not important' })
    export class MyEl {
      @bindable({ type: Number }) num;
    }
    @customElement({ name:'my-el', template: 'not important' })
    export class MyEl {
      @bindable({ nullable: false }) public num: number;
    }
    @customElement({ name:'my-el', template: 'not important' })
    export class MyEl {
      @bindable public num: number | string;
    }
    @customElement({ name:'my-el', template: 'not important' })
    export class MyEl {
      @bindable({type: String}) public num: number | string;
    }
    @customElement({ name:'my-el', template: 'not important' })
    export class MyEl {
      @bindable({set(v: unknown) {... return coercedV;}}) public num: number | string;
    }
    export class FormInput {
      @bindable label
      @bindable value
    }
    <label>${label}
      <input value.bind="value">
    </label>
    export class FormInput {
      @bindable label
      @bindable value
      @bindable type
      @bindable tooltip
      @bindable arias
      @bindable etc
    }
    <form-input
      label.bind="label"
      value.bind="message"
      tooltip.bind="Did you know Aurelia syntax comes from an idea of an Angular community member? We greatly appreciate Angular and its community for this."
      validation.bind="...">
    <label>${label}
      <input value.bind tooltip.bind validation.bind min.bind max.bind>
    </label>
    <label>${label}
      <input ...$attrs>
    </label>
    @customElement({
      ...,
      capture: true
    })
    import { capture } from 'aurelia';
    
    @capture
    export class MyCustomElement {
      ...
    }
    
    // either form is valid
    @capture()
    export class MyCustomElement {
      ...
    }
    <input ...$attrs>
    <input value.bind="..." ...$attrs> spread wins
    <input ...$attrs value.bind="..."> explicit wins
    <capture>
    
    <input ...$attrs>
    @customElement({
      capture: attr => attr !== 'class'
    })
    form-input.ts
    export class FormInput {
      @bindable label
    }
    my-app.html
    <form-input
      if.bind="needsComment"
      label.bind="label"
      value.bind="extraComment"
      class="form-control"
      style="background: var(--theme-purple)"
      tooltip="Hello, ${tooltip}">
    app.html
    <input-field value.bind="message">
    
    input-field.html
    <my-input ...$attrs>
    <!-- interpolation binding -->
    ${firstName}
    
    <!-- property binding -->
    <my-el prop.bind="address.pin"></my-el>
    foo.ts
    function foo() { return this.a ** 2; }
    foo.ts
    function foo() { return this.a ** 2; }
    
    const obj1 = { a: 10 };
    const obj2 = { a: 20 };
    
    console.log(foo.apply(obj1));       // 100
    console.log(foo.apply(obj2));       // 400
    App.ts
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'app',
      template: '<div>${message}</div>'
    })
    export class App implements ICustomElementViewModel {
      public readonly message: string = 'Hello World!';
      public readonly $controller: ICustomElementController<this>;
    
      public created(): void {
        const scope = this.$controller.scope;
        const bindingContext = scope.bindingContext;
        console.log(Object.is(bindingContext, this)); // true
        console.log(bindingContext.message);          // Hello World!
      }
    }
    +-----------------------+
    |                       |
    |  Scope                |
    |                       |
    |  +----------------+   |
    |  |                |   |
    |  | bindingContext |   |
    |  |                |   |
    |  +----------------+   |
    |                       |
    +-----------------------+
    App.ts
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'app',
      template: '<div>${message}</div>'
    })
    export class App implements ICustomElementViewModel {
      public readonly message: string = 'Hello World!';
      public readonly $controller: ICustomElementController<this>;
    
      public created(): void {
        const scope = this.$controller.scope;
        scope.overrideContext.message = 'Hello Aurelia!';
      }
    }
    +-----------------------+
    |                       |
    |  Scope                |
    |                       |
    |  +----------------+   |
    |  |                |   |
    |  | bindingContext |   |
    |  |                |   |
    |  +----------------+   |
    |                       |
    |                       |
    |  +-----------------+  |
    |  |                 |  |
    |  | overrideContext |  |
    |  |                 |  |
    |  +-----------------+  |
    |                       |
    +-----------------------+
    App.ts
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'app',
      template: '<let foo.bind="42"></let>${foo}'
    })
    export class App implements ICustomElementViewModel {
      public readonly $controller: ICustomElementController<this>;
    
      public attached(): void {
        const scope = this.$controller.scope;
        console.log('foo' in scope.bindingContext);  // false
        console.log(scope.overrideContext.foo);      // 42
      }
    }
    App.ts
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({ name: 'foo-bar', template: `\${message} \${$parent.message}` })
    export class FooBar implements ICustomElementViewModel {
      public readonly message: string = 'Hello Foo-Bar!';
      public readonly $controller: ICustomElementController<this>;
    
      public binding(): void {
        const scope = this.$controller.scope;
        console.log(scope.parentScope.bindingContext instanceof App); // true
      }
    }
    
    @customElement({
      name: 'app',
      template: '<foo-bar></foo-bar>',
      dependencies: [FooBar]
    })
    export class App implements ICustomElementViewModel {
      public readonly message: string = 'Hello App!';
      public readonly $controller: ICustomElementController<this>;
    
      public binding(): void {
        console.log(this.$controller.scope.parentScope); // null
      }
    }
        +----------------------------+    +----------------------------+
    +-->+                            |    |                            |
    |   |     Scope                  |    |     Scope                  |
    |   |                            |    |                            |
    |   |     +--------------+       |    |     +--------------+       |
    |   |     |              |       |    |     |              |       |
    |   |     | parentScope  |       |    |     | parentScope  +----------+
    |   |     |              |       |    |     |              |       |  |
    |   |     +--------------+       |    |     +--------------+       |  |
    |   |                            |    |                            |  |
    |   |     +----------------+     |    |     +----------------+     |  |
    |   |     |                |     |    |     |                |     |  |
    |   |     | bindingContext |     |    |     | bindingContext |     |  |
    |   |     |                |     |    |     |                |     |  |
    |   |     +----------------+     |    |     +----------------+     |  |
    |   |                            |    |                            |  |
    |   |     +-----------------+    |    |     +-----------------+    |  |
    |   |     |                 |    |    |     |                 |    |  |
    |   |     | overrideContext |    |    |     | overrideContext |    |  |
    |   |     |                 |    |    |     |                 |    |  |
    |   |     +-----------------+    |    |     +-----------------+    |  |
    |   |                            |    |                            |  |
    |   +----------------------------+    +----------------------------+  |
    |                                                                     |
    +---------------------------------------------------------------------+
    App.ts
    import {
      IPlatform,
    } from '@aurelia/kernel';
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'app',
      template: `\${message}`,
    })
    export class App implements ICustomElementViewModel {
      public message: string = 'Hello App!';
      public readonly $controller: ICustomElementController<this>;
      private intervalId: ReturnType<IPlatform['setInterval']>;
    
      public constructor(
        @IPlatform private readonly platform: IPlatform,
      ) { }
    
      public attached(): void {
        const scope = this.$controller.scope;
        let i = 1;
    
        this.intervalId = this.platform.setInterval(() => {
          scope.bindingContext.message = `Hello App! #i: ${i++}`;
        }, 1000);
    
        // this.intervalId = this.platform.setInterval(() => {
        //   this.message = `Hello App! #i: ${i++}`;
        // }, 1000);
      }
    
      public detaching(): void {
        this.platform.clearInterval(this.intervalId);
      }
    }
    App.ts
    import {
      IPlatform,
    } from '@aurelia/kernel';
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'app',
      template: `\${message}`,
    })
    export class App implements ICustomElementViewModel {
      public message: string = 'Hello App!';
      public readonly $controller: ICustomElementController<this>;
      private intervalId1: ReturnType<IPlatform['setInterval']>;
      private intervalId2: ReturnType<IPlatform['setInterval']>;
    
      public constructor(
        @IPlatform private readonly platform: IPlatform,
      ) { }
    
      public attached(): void {
        const scope = this.$controller.scope;
        let i = 1;
    
        this.intervalId1 = this.platform.setInterval(() => {
          scope.bindingContext.message = `Hello Binding Context! #i: ${i++}`;
        }, 1000);
    
        this.intervalId2 = this.platform.setInterval(() => {
          scope.overrideContext.message = `Hello Override Context! #i: ${i}`;
        }, 1000);
      }
    
      public detaching(): void {
        const platform = this.platform.
        platform.clearInterval(this.intervalId1);
        platform.clearInterval(this.intervalId2);
      }
    }
    App.ts
    import {
      IPlatform,
    } from '@aurelia/kernel';
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'app',
      template: `\${message}`,
    })
    export class App implements ICustomElementViewModel {
      public message: string = 'Hello App!';
      public readonly $controller: ICustomElementController<this>;
      private intervalId1: ReturnType<IPlatform['setInterval']>;
      private intervalId2: ReturnType<IPlatform['setInterval']>;
    
      public constructor(
        @IPlatform private readonly platform: IPlatform,
      ) { }
    
      public binding(): void {
        this.$controller.scope.overrideContext.message = 'Hello Override Context!';
      }
    
      public attached(): void {
        const scope = this.$controller.scope;
        let i = 1;
    
        this.intervalId1 = this.platform.setInterval(() => {
          scope.bindingContext.message = `Hello Binding Context! #i: ${i++}`;
        }, 1000);
    
        this.intervalId2 = this.platform.setInterval(() => {
          scope.overrideContext.message = `Hello Override Context! #i: ${i}`;
        }, 1000);
      }
    
      public detaching(): void {
        const platform = this.platform.
        platform.clearInterval(this.intervalId1);
        platform.clearInterval(this.intervalId2);
      }
    }
    App.ts
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'foo-bar',
      template: `<div repeat.for="i of 3">
      <div repeat.for="j of 2">
        \${message} \${$parent.i} \${j}
      </div>
      </div>` })
    export class FooBar implements ICustomElementViewModel {
      public readonly message: string = 'Hello Foo-Bar!';
    }
    
    @customElement({
      name: 'app',
      template: '<foo-bar></foo-bar>',
      dependencies: [FooBar]
    })
    export class App implements ICustomElementViewModel {
      public message: string = 'Hello App!';
    }
    Hello Foo-Bar! 0 0
    Hello Foo-Bar! 0 1
    Hello Foo-Bar! 1 0
    Hello Foo-Bar! 1 1
    Hello Foo-Bar! 2 0
    Hello Foo-Bar! 2 1
    0 0
    0 1
    1 0
    1 1
    2 0
    2 1
    App.ts
    import {
      customElement,
      ICustomElementController,
      ICustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({
      name: 'app',
      template: `\${message}`,
    })
    export class App implements ICustomElementViewModel {
      public message: string;
    
      public constructor(
        @IPlatform private readonly platform: IPlatform,
      ) { }
    
      public attached(): void {
        const platform = this.platform;
        const id = platform.setTimeout(() => {
          this.message = 'Hello World!';
          platform.clearTimeout(id);
        }, 2000);
      }
    }
    router-lite - hook - mixed registration - StackBlitzStackBlitz
    router-lite - hook as dependencies - StackBlitzStackBlitz
    Logo
    Logo
    router-lite - globally registered hooks - StackBlitzStackBlitz
    router-lite - hooks - preemption - StackBlitzStackBlitz
    router-lite - lifecycle-hooks - Auth - StackBlitzStackBlitz

    Errors

    Error messages

    Encountered an error and looking for answers? You've come to the right place.

    This section is a work in progress and not yet complete. If you would like to help us document errors in Aurelia, we welcome all contributions.

    Coded error in Aurelia comes with format: AURxxxx:yyyy where:

    • AUR is the prefix to indicate it's an error from Aurelia

    • xxxx is the code

    • : is the delimiter between the prefix, code and the dynamic information associated with the error

    The section below will list errors by their prefix, and code and give a corresponding explanation, and a way to fix them.

    Dependency Injection Errors (from 0001 to 0015)

    Dependency Injection errors can be found .

    Template Compiler Errors (From 701-749)

    Error Code
    Description

    Templating Errors (From 750-800)

    Error Code
    Description

    HTML observation errors

    Error Code
    Description

    Controller errors

    Error Code
    Description

    Default resources errors

    Error Code
    Description

    Plugin errors

    Error Code
    Plugin name
    Description

    Runtime module

    AST errors (from 101 to 150)

    Error Code
    Description

    Parser errors (from 151-200)

    Error Code
    Description

    Others (from 200-300)

    Error Code
    Description
    Logo
    Logo
    Logo

    yyyy is the extra information, or parameters related to the error

    AUR0706

    This happens when [au-slot] attribute is used on an element that is not an immediate child of a custom element

    AUR0707

    This happens when the template compiler encounters binding to a non-bindable property of a custom attribute

    AUR0708

    This happens when the template of a custom element has nothing beside template elements with as-local-element

    AUR0709

    This happens when an as-local-element template is not defined as an immediate child of the root of a custom element template

    AUR0710

    This happens when an as-local-element template has a <bindable> element inside its template, that is not not an immediate child of its fragment

    AUR0711

    This happens when a <bindable> inside an as-local-element template does not have a valid property attribute on it

    AUR0712

    This happens when an as-local-element template has 2 or more <bindable> elements with non-unique attribute or property attributes

    AUR0713

    This happens when an unknown binding command is encountered in a custom element template

    AUR0714

    This happens when a custom element or attribute definition has more than 1 primary bindable property

    AUR0715

    This happens when an as-local-template template has the value of as-local-template as an empty string

    AUR0716

    This happens when a custom element has 2 or more local elements with the same name

    AUR0755

    This happens when a view factory provider tries to resolve but does not have a view factory associated

    AUR0756

    This happens when a view factory provider tries to resolve but the view factory associated does not have a valid name

    AUR0757

    This happens when IRendering.render is called with different number of targets and instructions

    AUR0758

    This happens when BindingCommand.getDefinition is called on a class/object without any binding command metadata associated

    AUR0759

    This happens when CustomAttribute.getDefinition is called on a class/object without any custom attribute metadata associated

    AUR0760

    This happens when CustomElement.getDefinition is called on a class/object without any custom element metadata associated

    AUR0761

    This happens when CustomElementDefinition.create is called with a string as first parameter

    AUR0762

    This happens when CustomElement.for is called on an element that does not have any custom element with a given name, without searching in ancestor elements

    AUR0763

    This happens when CustomElement.for is called and Aurelia isn't able to find any custom element with the given name in the given element, or its ancestors

    AUR0764

    This happens when CustomElement.for is called on an element with a given name, and Aurelia is unable to find any custom element in the given the element, or its ancestors

    AUR0765

    This happens when CustomElement.for is called on an element without a given name, and Aurelia is unable to find any custom element in the given element, or its ancestors

    AUR0766

    This happens when @processContent is called with a string as its first parameter, and Aurelia couldn't find the method on the decorated class

    AUR0767

    This happens when root property on an Aurelia instance is access before at least one application has been started with this Aurelia instance

    AUR0768

    This happens when a new Aurelia is created with a predefined container that already has IAurelia registration in it, or its ancestors

    AUR0769

    This happens when an Aurelia application is started with a document fragment before it's adopted by a document

    AUR0770

    This happens when Aurelia.prototype.start is called with a null/undefined value as the first parameter

    AUR0771

    This happens when Aurelia.prototype.dispose is called before the instance is stopped

    AUR0772

    This happens when the @watch decorator is used without a valid first parameter

    AUR0773

    This happens when the @watch decorator is used and Aurelia is not able to resolve the first parameter to a function

    AUR0774

    This happens when the @watch decorator is used on a class property instead of a method

    AUR0505

    This happens when the internal state of a controller is coruppted during deactivation

    AUR0506

    This happens when Aurelia fails to resolve a function from the first parameter of a @watch decorator

    AUR0806

    This happens when <au-compose> component binding is used with a custom element with containerless = true

    AUR0807

    This happens when there's a corrupted internal state of <au-compose> and activation is called twice

    AUR0808

    This happens when there's a corrupted internal state of <au-compose> and deactivation is called twice

    AUR0809

    This happens when <au-render> component binding is given a string value, and there's no custom element with matching name

    AUR0810

    This happens when else attribute does not follow an if attribute

    AUR0811

    This happens when portal attribute is a given an empty string as CSS selector fortarget, and strict mode is on

    AUR0812

    This happens when portal attribute couldn't find the target element to portal to, and strict mode is on

    AUR0813

    This happens when then/catch/pending attributes is used outside of a promise attribute

    AUR0814

    This happens when the internal of the repeat attribute get into a race condition and is corrupted

    AUR0815

    This happens when case/default-case attributes is used outside of a switch attribute

    AUR0816

    This happens when there are multiple default-case attributes inside a switch attribute

    AUR0817

    This happens when & signal binding behavior is used on binding that does not have handleChange method

    AUR0818

    This happens when & signal binding behavior is used without a valid name (non empty)

    Dialog

    This happens when the default configuration of the dialog plugin is used, as there's no registration associated for key interfaces

    AUR0106

    This happens when an expression looks like this $host = ..., as $host is a readonly property

    AUR0107

    This happens when a call expression is evaluated but the object evaluated by the expression isn't a function

    AUR0108

    This happens when a binary expression is evaluated with an unknown operator

    AUR0109

    This happens when an unary expression is evaluated with an unknown operator

    AUR0110

    This happens when a tagged template (function call) is but the function specified isn't a function

    AUR0111

    This happens when a function call AST is evaluated but no function is found

    AUR0112

    This happens when a non-object or non-array value is assigned for destructured declaration for a repeat.for statement

    AUR0156

    The parser encounters an unconsumable token in an expression

    AUR0158

    The expression has an invalid assignment

    AUR0159

    An expression has no valid identifier after the value converter ` | ` symbol

    AUR0160

    An expression has no valid identifier after the binding behavior & symbol

    AUR0161

    The parser encounters an invalid of keyword

    AUR0162

    The parser encounters an unconsumed token

    AUR0163

    The parser encounters an invalid binding identifier at left hand side of an of keyword

    AUR0164

    The parser encounters a literal object with a property declaration that it doesn't understand

    AUR0165

    An expression has an opening string quote ' or ", but no matching ending quote

    AUR0166

    An expression has an opening template string quote ```, but has no matching end

    AUR0167

    The parser encounters an unexpected token

    AUR0168

    The parser encounters an unexpected character

    AUR0169

    The parser encounters an unexpected character while parsing destructuring assignment expression

    AUR0206

    ConnectableSwitcher.enter is called with null/undefined as the first parameter

    AUR0207

    ConnectableSwitcher.enter is called with the currently active connectable

    AUR0208

    ConnectableSwitcher.exit is called with null/undefined as the first parameter

    AUR0209

    ConnectableSwitcher.exit is called with an inactive connectable

    AUR0210

    getCollectionObserver is called with an not-supported collection type

    AUR0211

    a binding subscried to an observer, but does not implement method handleChange

    AUR0212

    a binding subscribed to a collection observer, but does not implement method handleCollectionChange

    AUR0220

    a Set/Map size observer .setValue method is called

    AUR0221

    the setValue method on a computed property without a setter

    AUR0222

    Aurelia doesn't know how to observe a property on an object, and dirty checking is disabled

    AUR0224

    Encounters an invalid usage of @observable

    AUR0225

    An effect is attempted to run again, after it has stopped

    AUR0226

    An effect has reach its limit of recursive update

    AUR0701

    This happens when a template has a single template element in your template, and it has as-local-element attribute on it

    AUR0702

    This happens when a template has one or more attributes that are supposed to be unique on its surrogate elements

    AUR0703

    This happens when a template controller attribute is used on a surrogate element of a template

    AUR0704

    This happens when an attribute on a <let/> element is used without .bind or .to-view command

    AUR0705

    This happens when enhancing a template with one or more element in it already have a class au on it

    AUR0750

    This happens when there is a binding that looks like this view.ref="...". This likely comes from a v1 template migration.

    AUR0751

    This happens when there is a ref binding in the template that does not have matching target. Most likely a custom attribute reference

    AUR0752

    This happens when a controller renders a custom element instruction that it doesn't have a registration. Normally happens in hand-crafted definition

    AUR0753

    This happens when a controller renders a custom attribute instruction that it doesn't have a registration. Normally happens in hand-crafted definition

    AUR0754

    This happens when a controller renders a template controller instruction that it doesn't have a registration. Normally happens in hand-crafted definition

    AUR0651

    This happens when the binding created .attr binding command is forced into two way mode against any attribute other than class/style

    AUR0652

    This happens when the default NodeObserverLocator.getObserver is called with an object and property combo that it doesn't know how to observe, and dirty checking is disabled

    AUR0653

    This happens when NodeObserverLocator property->observation events mapping is getting overridden

    AUR0654

    This happens when a <select> element is specified multiple, but the binding value is not an array

    AUR0500

    This happens when Controller.getCachedOrThrow throws

    AUR0501

    This happens when a custom element is specified containerless and has <slot> element in its template

    AUR0502

    This happens when a disposed controller is being activated

    AUR0503

    This happens when the internal state of a controller is corrputed during activation

    AUR0504

    This happens when a synthetic view is activated without a proper scope

    AUR0801

    This happens when & self binding behavior is used on non-event binding

    AUR0802

    This happens when & updateTrigger binding behavior is used without any arguments

    AUR0803

    This happens when & updateTrigger binding behavior is used on binding without view -> view model observation

    AUR0804

    This happens when & updateTrigger binding behavior is used on binding that does not target a DOM element

    AUR0805

    This happens when <au-compose> scopeBehavior property is assigned a value that is not either auto or scoped

    AUR0901

    Dialog

    This happens when an application is closed with some dialogs still open

    AUR0902

    Dialog

    This happens when DialogController injection is requested. It's a error prevention for v1->v2 migration of the dialog plugin

    AUR0903

    Dialog

    This happens when IDialogService.open is called without both component and template property

    AUR0101

    This happens when Aurelia couldn't find a binding behavior specified in an expression

    AUR0102

    This happens when there are two binding behaviors with the same name in an expression

    AUR0103

    This happens when a value converter for a given name couldn't be found during the evaluation of an expression

    AUR0104

    This happens when a value converter for a given name couldn't be found during the assignment of an expression

    AUR0105

    This happens when the special $host contextual property is accessed but no thing is found in the scope tree

    AUR0151

    An expression has an invalid character at the start

    AUR0152

    An expression has .. or ...

    AUR0153

    The parser encounters an unexpected identifier in an expression

    AUR0154

    The parser encounters an invalid AccessMember expression

    AUR0155

    The parers encounters an unexpected end in an expression

    AUR0201

    BindingBehavior.getDefinition is called on a class/object without any binding behavior metadata associated

    AUR0202

    ValueConverter.getDefinition is called on a class/object without any value converter metadata associated

    AUR0203

    BindingContext.get is called with null/undefined as the first parameter

    AUR0204

    Scope.fromOverride is called with null/undefined as the first parameter

    AUR0205

    Scope.fromParent is called with null/undefined as the first parameter

    here

    AUR0904

    Form Inputs

    Handling forms and user input is quite a common task in applications. Whether you are building a login form, data entry, or even a chat application, Aurelia allows you to work with forms intuitively.

    In Aurelia, the binding system uses two-way binding as a default for form elements. Text inputs, text areas and even contenteditable elements all use a two-way binding.

    Many of the concepts discussed here assume knowledge of how Aurelia's template and binding syntax work. We recommend reading the Template syntax & features section before continuing with this section if you are new to Aurelia.

    Data flow in forms

    In Aurelia, form elements are reactive, and their changes are directly tied to the underlying view model. Updates flow from the view to the view model, and updates from the view model flow to the view (hence, two-way).

    To illustrate how two-way binding works in forms, let's break down the workflow:

    1. The user types a value into the input element. The element is for a first name, so they enter John.

    2. The native form input events are fired, and Aurelia also sees the value has changed.

    3. The binding system sees the new value and notifies the view model to update the value.

    4. Any reference to the bound value will be updated without needing any callback functions or additional notification steps (the value changes).

    Creating a basic form

    Creating forms in Aurelia requires no special configuration or treatment. Create a form element and add form input controls with bindings. Here is a basic form example for a login form to show you how little code you need.

    Firstly, let's create the markup for our login form:

    Before we write the view model code, let's break down what we did here:

    • We created a form with two text inputs

    • We used value.bind to bind the native value attribute of these fields to the corresponding view model properties

    • We are calling a function handleLogin when the submit event on the form is triggered to handle the bindable properties inside

    Now, the corresponding view model code:

    There is not a whole lot of code here for what is happening. Whenever the email or password values change, they will be reflected inside of our view model. Inside the handleLogin method, we would probably validate the data and call an API.

    Using submit.trigger on a form will prevent its default action by applying a event.preventDefault behind-the-scenes. This means your form will not submit to the action or method attributes on the form, you will need to handle this manually.

    Binding with text and textarea inputs

    Binding to text inputs uses a syntax similar to binding to other elements in Aurelia. By default, input elements will use two-way binding, which means the value will update in the view when changed inside the view model and updated in the view model when changed in the view.

    Text Input

    You can even bind to other attributes on form elements such as the placeholder attribute.

    Textarea

    A textarea element is just like any other form element. It allows you to bind to its value and, by default, value.bind will be two-way binding (meaning changes flow from out of the view into the view model and changes in the view-model flow back to the view).

    Binding with checkbox inputs

    Aurelia supports the two-way binding of various data types to checkbox input elements.

    Booleans

    Bind a boolean property to an input element's checked attribute using checked.bind="myBooleanProperty".

    Array of Numbers

    A set of checkbox elements is a multiple-selection interface. If you have an array that serves as the "selected items" list, you can bind the array to each input's checked attribute. The binding system will track the input's checked status, adding the input's value to the array when the input is checked and removing the input's value from the array when the input is unchecked.

    To define the input's "value", bind the input's model attribute: model.bind="product.id".

    Array of Objects

    Numbers aren't the only type of value you can store in a "selected items" array. The binding system supports all types, including objects. Here's an example that adds and removes "product" objects from a selectedProducts array using the checkbox data-binding.

    Array of Objects with Matcher

    You may run into situations where the object your input element's model is bound to do not have reference equality to any objects in your checked array. The objects might match by id, but they may not be the same object instance. To support this scenario, you can override Aurelia's default "matcher", which is an equality comparison function that looks like this: (a, b) => a === b.

    You can substitute your chosen function with the right logic to compare your objects.

    Array of Strings

    Finally, here's an example that adds and removes strings from an selectedProducts array using the checkbox data-binding. This is example is unique because it does not use model.bind to assign each checkbox's value. Instead, the input's standard value attribute is used.

    Normally we cannot use the standard value attribute in conjunction with checked binding because it coerces anything assigned to a string. This example uses an array of strings, so everything works just fine.

    Binding with radio inputs

    A radio input group is a "single select" interface. Aurelia supports two-way binding any type of property to a group of radio inputs. The examples below illustrate binding number, object, string and boolean properties to sets of radio inputs. In each of the examples, there's a common set of steps:

    1. Group the radios via the name property. Radio buttons with the same value for the name attribute are in the same "radio button group"; only one radio button in a group can be selected at a time.

    2. Define each radio's value using the model property.

    3. Two-way bind each radio's checked attribute to a "selected item" property on the view model.

    Numbers

    Let's start with an example that uses a numeric "selected item" property. Each radio input will be assigned a number value via the model property in this example. Selecting a radio will cause its model value to be assigned to the selectedProductId property.

    Objects

    The binding system supports binding all types of radios, including objects. Here's an example that binds a group of radios to a selectedProduct object property.

    Objects with Matcher

    You may run into situations where the object your input element's model is bound to does not have reference equality to any of the objects in your checked attribute bound to. The objects might match by id, but they may not be the same object instance. To support this scenario, you can override Aurelia's default "matcher", which is an equality comparison function that looks like this: (a, b) => a === b.

    You can substitute your chosen function with the right logic to compare your objects.

    Booleans

    In this example, each radio input is assigned one of three literal values: null, true and false. Selecting one of the radios will assign its value to the likesCake property.

    Strings

    Finally, here's an example using strings. This is example is unique because it does not use model.bind to assign each radio's value. Instead, the input's standard value attribute is used. Normally we cannot use the standard value attribute in conjunction with checked binding because it coerces anything assigned to a string.

    Binding with select elements

    A <select> element can serve as a single-select or multiple-select "picker", depending on whether the multiple attribute is present. The binding system supports both use cases. The samples below demonstrate a variety of scenarios.

    All use a common series of steps to configure the selected element:

    1. Add a <select> element to the template and decide whether the multiple attribute should be applied.

    2. Bind the select element's value attribute to a property. In "multiple" mode, the property should be an array. In singular mode, it can be any type.

    3. Define the select element's <option>

    Select Number

    Select Object

    Select Object with Matcher

    You may run into situations where the object to your select element's value is bound and does not have reference equality with any of the objects your option element model properties are bound to. The select's value object might "match" one of the option objects by id, but they may not be the same object instance.

    To support this scenario, you can override Aurelia's default "matcher", which is an equality comparison function that looks like this: (a, b) => a === b. You can substitute your chosen function with the right logic to compare your objects.

    Select Boolean

    Select String

    Multiple Select Numbers

    Multiple Select Objects

    Multiple Select Strings

    Form Submission

    Most of the time, a <form> element should be used to group one or many controls in a form. It acts as a container for those controls and can also be used for layout purposes with CSS.

    Normally, HTML forms can be submitted without involving any JavaScript via the action and method attributes on a <form>. Though it's also common in applications that forms are driven by JavaScript.

    In Aurelia, driving form via script can be achieved via submit event on the form, with the basic usage looking like the following example:

    Note that by default, for a <form/> without a method attribute, or method attribute value being equal to GET/get, using submit.trigger will call preventDefault() on the submit event, which prevents the normally unwanted behavior of html of navigating the page to the URI of the form. If this behavior is not desired, return true in the method being called, like the following example:

    Form validation

    Validation is an important part of creating good forms. Aurelia provides a robust validation plugin that allows you to validate forms, create custom validation rules and configure every facet of validation in your Aurelia applications.

    To learn about form validation using the Aurelia Validation package, please consult the validation documentation below for details.

    router-lite - named-viewport - route-config - StackBlitzStackBlitz
    router-lite - viewport - fallback - StackBlitzStackBlitz
    router-lite - hierarchical-viewport - StackBlitzStackBlitz
    router-lite - viewport - used-by - StackBlitzStackBlitz
    router-lite - viewport - default - StackBlitzStackBlitz
    router-lite - viewport - fallback - using function - StackBlitzStackBlitz
    router-lite - viewport - used-by - with default - StackBlitzStackBlitz
    router-lite - sibling-viewport - duplicate - StackBlitzStackBlitz
    router-lite - named-viewport - StackBlitzStackBlitz
    router-lite - sibling-viewport - no duplicate - StackBlitzStackBlitz
    elements. You can use
    repeat
    or add each option element manually.
  • Specify each option's value via the model property:

    <option model.bind="product.id">${product.name}</option>

    You can use the standard value attribute instead of model, remember- it will coerce anything it's assigned to a string.

  • Validation
    login-component.html
    <form submit.trigger="handleLogin()">
        <div>
            <label for="email">Email:</label>
            <input id="email" type="text" value.bind="email">
        </div>
        <div>
            <label for="password">Password:</label>
            <input id="password" type="password" value.bind="password">
        </div>
        
        <button type="submit">Login</button>
    </form>
    login-component.ts
    export class LoginComponent {
        private email = '';
        private password = '';
        
        // This function is called when the form is submitted
        handleLogin() {
            // Call an API/validate the bound values
        }
    }
    <form>
      <label>User value</label><br>
      <input type="text" value.bind="userValue" />
    </form>
    <form>
      <label>User value</label><br>
      <input type="text" value.bind="userValue" placeholder.bind="myPlaceholder" />
    </form>
    <form role="form">
      <textarea value.bind="textAreaValue"></textarea>
    </form>
    export class App {
      motherboard = false;
      cpu = false;
      memory = false;
    }
    <template>
      <form>
        <h4>Products</h4>
        <label><input type="checkbox" checked.bind="motherboard">  Motherboard</label>
        <label><input type="checkbox" checked.bind="cpu"> CPU</label>
        <label><input type="checkbox" checked.bind="memory"> Memory</label>
    
        motherboard = ${motherboard}<br>
        cpu = ${cpu}<br>
        memory = ${memory}<br>
      </form>
    </template>
    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      selectedProductIds = [];
    }
    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="checkbox" model.bind="product.id" checked.bind="selectedProductIds">
          ${product.id} - ${product.name}
        </label>
        <br>
        Selected product IDs: ${selectedProductIds}
      </form>
    </template>
    export interface IProduct {
      id: number;
      name: string;
    }
    
    export class App {
      products: IProduct[] = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      selectedProducts: IProduct[] = [];
    }
    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="checkbox" model.bind="product" checked.bind="selectedProducts">
          ${product.id} - ${product.name}
        </label>
    
        Selected products:
        <ul>
          <li repeat.for="product of selectedProducts">${product.id} - ${product.name}</li>
        </ul>
      </form>
    </template>
    export class App {
      selectedProducts = [
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' }
      ];
    
      productMatcher = (a, b) => a.id === b.id;
    }
    <template>
      <form>
        <h4>Products</h4>
        <label>
          <input type="checkbox" model.bind="{ id: 0, name: 'Motherboard' }"
                  matcher.bind="productMatcher"
                  checked.bind="selectedProducts">
          Motherboard
        </label>
        <label>
          <input type="checkbox" model.bind="{ id: 1, name: 'CPU' }"
                  matcher.bind="productMatcher"
                  checked.bind="selectedProducts">
          CPU
        </label>
        <label>
          <input type="checkbox" model.bind="{ id: 2, name: 'Memory' }"
                  matcher.bind="productMatcher"
                  checked.bind="selectedProducts">
          Memory
        </label>
    
        Selected products:
        <ul>
          <li repeat.for="product of selectedProducts">${product.id} - ${product.name}</li>
        </ul>
      </form>
    </template>
    export class App {
      products = ['Motherboard', 'CPU', 'Memory'];
      selectedProducts = [];
    }
    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="checkbox" value.bind="product" checked.bind="selectedProducts">
          ${product}
        </label>
        <br>
        Selected products: ${selectedProducts}
      </form>
    </template>
    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      selectedProductId = null;
    }
    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="radio" name="group1"
                  model.bind="product.id" checked.bind="selectedProductId">
          ${product.id} - ${product.name}
        </label>
        <br>
        Selected product ID: ${selectedProductId}
      </form>
    </template>
    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      selectedProduct = null;
    }
    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="radio" name="group2"
                  model.bind="product" checked.bind="selectedProduct">
          ${product.id} - ${product.name}
        </label>
    
        Selected product: ${selectedProduct.id} - ${selectedProduct.name}
      </form>
    </template>
    export class App {
      selectedProduct = { id: 1, name: 'CPU' };
    
      productMatcher = (a, b) => a.id === b.id;
    }
    <template>
      <form>
        <h4>Products</h4>
        <label>
          <input type="radio" name="group3"
                  model.bind="{ id: 0, name: 'Motherboard' }"
                  matcher.bind="productMatcher"
                  checked.bind="selectedProduct">
          Motherboard
        </label>
        <label>
          <input type="radio" name="group3"
                  model.bind="{ id: 1, name: 'CPU' }"
                  matcher.bind="productMatcher"
                  checked.bind="selectedProduct">
          CPU
        </label>
        <label>
          <input type="radio" name="group3"
                  model.bind="{ id: 2, name: 'Memory' }"
                  matcher.bind="productMatcher"
                  checked.bind="selectedProduct">
          Memory
        </label>
    
        Selected product: ${selectedProduct.id} - ${selectedProduct.name}
      </form>
    </template>
    export class App {
      likesCake = null;
    }
    <template>
      <form>
        <h4>Do you like cake?</h4>
        <label>
          <input type="radio" name="group3"
                  model.bind="null" checked.bind="likesCake">
          Don't Know
        </label>
        <label>
          <input type="radio" name="group3"
                  model.bind="true" checked.bind="likesCake">
          Yes
        </label>
        <label>
          <input type="radio" name="group3"
                  model.bind="false" checked.bind="likesCake">
          No
        </label>
    
        likesCake = ${likesCake}
      </form>
    </template>
    export class App {
      products = ['Motherboard', 'CPU', 'Memory'];
      selectedProduct = null;
    }
    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="radio" name="group4"
                  value.bind="product" checked.bind="selectedProduct">
          ${product}
        </label>
        <br>
        Selected product: ${selectedProduct}
      </form>
    </template>
    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      selectedProductId = null;
    }
    <template>
      <label>
        Select product:<br>
        <select value.bind="selectedProductId">
          <option model.bind="null">Choose...</option>
          <option repeat.for="product of products"
                  model.bind="product.id">
            ${product.id} - ${product.name}
          </option>
        </select>
      </label>
      Selected product ID: ${selectedProductId}
    </template>
    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      selectedProduct = null;
    }
    <template>
      <label>
        Select product:<br>
        <select value.bind="selectedProduct">
          <option model.bind="null">Choose...</option>
          <option repeat.for="product of products"
                  model.bind="product">
            ${product.id} - ${product.name}
          </option>
        </select>
      </label>
    
      Selected product: ${selectedProduct.id} - ${selectedProduct.name}
    </template>
    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      productMatcher = (a, b) => a.id === b.id;
    
      selectedProduct = { id: 1, name: 'CPU' };
    }
    <template>
      <label>
        Select product:<br>
        <select value.bind="selectedProduct" matcher.bind="productMatcher">
          <option model.bind="null">Choose...</option>
          <option repeat.for="product of products"
                  model.bind="product">
            ${product.id} - ${product.name}
          </option>
        </select>
      </label>
    
      Selected product: ${selectedProduct.id} - ${selectedProduct.name}
    </template>
    export class App {
      likesTacos = null;
    }
    <template>
      <label>
        Do you like tacos?:
        <select value.bind="likesTacos">
          <option model.bind="null">Choose...</option>
          <option model.bind="true">Yes</option>
          <option model.bind="false">No</option>
        </select>
      </label>
      likesTacos: ${likesTacos}
    </template>
    export class App {
      products = ['Motherboard', 'CPU', 'Memory'];
      selectedProduct = '';
    }
    <template>
      <label>
        Select product:<br>
        <select value.bind="selectedProduct">
          <option value="">Choose...</option>
          <option repeat.for="product of products"
                  value.bind="product">
            ${product}
          </option>
        </select>
      </label>
      Selected product: ${selectedProduct}
    </template>
    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      selectedProductIds = [];
    }
    <template>
      <label>
        Select products:
        <select multiple value.bind="selectedProductIds">
          <option repeat.for="product of products"
                  model.bind="product.id">
            ${product.id} - ${product.name}
          </option>
        </select>
      </label>
      Selected product IDs: ${selectedProductIds}
    </template>
    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];
    
      selectedProducts = [];
    }
    <template>
      <label>
        Select products:
        <select multiple value.bind="selectedProducts">
          <option repeat.for="product of products"
                  model.bind="product">
            ${product.id} - ${product.name}
          </option>
        </select>
      </label>
    
      Selected products:
      <ul>
        <li repeat.for="product of selectedProducts">${product.id} - ${product.name}</li>
      </ul>
    </template>
    export class App {
      products = ['Motherboard', 'CPU', 'Memory'];
      selectedProducts = [];
    }
    <template>
      <label>
        Select products:
        <select multiple value.bind="selectedProducts">
          <option repeat.for="product of products"
                  value.bind="product">
            ${product}
          </option>
        </select>
      </label>
      Selected products: ${selectedProducts}
    </template>
    <form submit.trigger="submitMyForm()">
      ...
    </form>
    class MyApp {
      submitMyForm() {
        fetch('/register', { method: 'POST', ... })
      }
    }
    class MyApp {
      submitMyForm() {
        ...
        return true;
      }
    }
    Logo
    Logo
    Logo
    Logo
    Logo
    router-lite - sibling-viewport - StackBlitzStackBlitz
    router-lite - viewport - used-by - multiple values - StackBlitzStackBlitz
    Logo
    Logo
    Logo
    Logo
    Logo

    Slotted content

    Learn how to work with slots to work with the concept of dynamic slot placeholders in your custom elements.

    In Aurelia, we have two ways to project content into custom elements. In the case of Shadow DOM, we can use <slot> and for situations where Shadow DOM is not desirable, we have <au-slot>

    Slot

    When working with Shadow DOM-enabled components, the <slot> element is a web platform native way to allow content projection into components. In some instances, the <slot>

    Logo
    Logo
    element will not be the right choice, and you will need to consider
    instead.

    The slot element will only work when Shadow DOM is enabled for your component. Attempting to use the slot element with it disabled will throw an error in the console.

    In the case of a fictional but realistic example, we have a modal element. The user can provide content which is rendered inside of the element.

    Assuming this is a Shadow DOM-enabled component, all is well. We have a custom element that allows for content to be used inside of it.

    Because we named our component au-modal we will then use it like this:

    Notice how we use the attribute slot on our content being passed in? This tells Aurelia to project our content into the default slot. Custom elements can have multiple slots, so how do we tell Aurelia where to project our content?

    Named slots

    A named slot is no different to a conventional slot. The only difference is the slot has a name we can reference. A slot without a name gets the name default by default.

    Now, to use our element with a named slot, you can do this:

    Fallback content

    A slot can display default content when nothing is explicitly projected into it. Fallback content works for default and named slot elements.

    Listening to projection change

    At the projection target (<slot> element), with the slotchange event

    The <slot> element comes with an event based way to listen to its changes. This can be done via listening to slotchange even on the <slot> element, like the following example:

    At the projection source (custom element host), with the @children decorator

    In case where it's not desirable to go to listen to projection change at the targets (<slot> elements), it's also possible to listen to projection at the source with @children decorator. Decorating a property on a custom element class with @children decorator will setup mutation observer to notify of any changes, like the following example:

    {% code title="my-app.html" overflow="wrap" lineNumbers="true" }

    After the initial rendering, myDetails.divs will be an array of 1 <div> element, and any future addition of any <div> elements to the <my-details> element will update the divs property on myDetails instance, with corresponding array.

    Additionally, the @children decorator will also call a callback as a reactive change handler. The name of the callback, if omitted in the decorator, will be derived based on the property being decorated, example: divs -> divsChanged

    @children decorator usage

    Usage
    Meaning

    @children() prop

    Use default options, observe mutation, and select all elements

    @children('div') prop

    Observe mutation, and select only div elements

    Note: the `@children` decorator wont update if the children of a slotted node change — only if you change (e.g. add or delete) the actual nodes themselves. {% %}

    Au-slot

    Aurelia provides another way of content projection with au-slot. This is similar to the native slot when working with content projection. However, it does not use Shadow DOM. au-slot is useful where you want externally defined styles to penetrate the component boundary, facilitating easy styling of components.

    Suppose you create your own set of custom elements solely used in your application. In that case, you might want to avoid the native slots in the custom elements, as it might be difficult to style them from your application.

    However, if you still want slot-like behavior, then you can use au-slot, as that makes styling those custom elements/components easier. Instead of using shadow DOM, the resulting view is composed purely by the Aurelia compilation pipeline.

    There are other aspects of au-slot as well which will be explored in this section with examples.

    An obvious question might be, "Why not simply 'turn off' shadow DOM, and use the slot itself"? We feel that goes opposite to Aurelia's promise of keeping things as close to native behavior as possible. Moreover, using a different name like au-slot makes it clear that the native slot is not used in this case. However, still brings slotting behavior to use.

    If you have used the replaceable and replace part before or with Aurelia1, it is replaced with au-slot.

    Basic templating usage

    Like slot, a "projection target"/"slot" can be defined using a <au-slot> element, and a projection to that slot can be provided using a [au-slot] attribute. Consider the following example.

    In the example above, the my-element custom element defines two slots: one default and one named. The slots can optionally have fallback content; i.e. when no projection is provided for the slot, the fallback content will be displayed. Projecting to a slot is, therefore, also optional. However, when a projection is provided for a slot, that overrides the fallback content of that slot.

    Similar to native shadow DOM and <slot/>/[slot] pair, [au-slot] attribute is not mandatory if you are targeting the default slot. All content without explicit [au-slot] is treated as targeting the default slot. Having no [au-slot] is also equal to having explicit au-slot on the content:

    Another important point to note is that the usage of [au-slot] attribute is supported only on the direct children elements of a custom element. This means that the following examples do not work.

    Inject the projected slot information

    It is possible to inject an instance of IAuSlotsInfo in a custom element view model. This provides information related to the slots inside a custom element. The information includes only the slot names for which content has been projected. Let's consider the following example.

    The followingrk would be logged to the console for the instances of my-element.

    Binding scope

    It is also possible to use data-binding, interpolation etc., while projecting. While doing so, the scope accessing rule can be described by the following thumb rule:

    1. When the projection is provided, the scope of the custom element providing the projection is used.

    2. When the projection is not provided, the scope of the inner custom element is used.

    3. The outer custom element can still access the inner scope using the $host keyword while projecting.

    Examples

    To further explain how these rules apply, these rules are explained with the following examples.

    Projection uses the outer scope by defaultthe

    Let's consider the following example with interpolation.

    Although the my-element has a message property, but as my-app projects to s1 slot, scope of my-app is used to evaluate the interpolation expression. Similar behavior can also be observed when binding properties of custom elements, as shown in the following example.

    Fallback uses the inner scope by default

    Let's consider the following example with interpolation. This is the same example as before, but this time without projection.

    Note that in the absence of projection, the fallback content uses the scope of my-element. For completeness, the following example shows that it also holds while binding values to the @bindables in custom elements.

    Access the inner scope with $host

    The outer custom element can access the inner custom element's scope using the $host keyword, as shown in the following example.

    Note that using the $host.message expression, MyApp can access the MyElement#message. The following example demonstrates the same behavior for binding values to custom elements.

    Let's consider another example of $host which highlights the communication between the inside and outside of a custom element that employs <au-slot>

    In the example above, we replace the 'content' template of the grid, defined in my-element, from my-app. While doing so, we can grab the scope of the <au-slot name="content" /> and use the properties made available by the binding expose.bind="{ person, $even, $odd, $index }", and use those in the projection template.

    Note that $host allows us to access whatever the <au-slot/> element exposes, and this value can be changed to enable powerful scenarios. Without the $host it might not have been easy to provide a template for the repeater from the outside.

    The last example is also interesting from another aspect. It shows that many parts of the grid can be replaced with projection while working with a grid. This includes the header of the grid (au-slot="header"), the template column of the grid (au-slot="content"), or even the whole grid itself (au-slot="grid").

    The $host keyword can only be used in the context of projection. Using it in any other context is not supported and will throw errors with high probability.

    Multiple projections for a single slot

    It is possible to provide multiple projections to a single slot.

    This is useful for many cases. One evident example would a 'tabs' custom element.

    This helps keep things closer that belong together. For example, keeping the tab-header and tab-content next to each other provides better readability and understanding of the code to the developer. On other hand, it still places the projected contents in the right slot.

    Duplicate slots

    Having more than one <au-slot> with the same name is also supported. This lets us project the same content to multiple slots declaratively, as can be seen in the following example.

    Note that projection for the name is provided once, but it gets duplicated in 2 slots. You can also see this example in action here.

    Listening to <au-slot> change

    Similar like the standard <slot> element allows the ability to listen to changes in the content projected, <au-slot> also provides the capability to listen & react to changes.

    With @slotted decorator

    One way to subscribe to au-slot changes is via the @slotted decorator, like the following example:

    After rendering, the MySummaryElement instance will have paragraphs value as an array of 2 <p> element as seen in the app.html.

    The @slotted decorator will invoke change handler upon initial rendering, and whenever there's a mutation after wards, while the owning custom element is still active. By default, the callback will be selected based on the name of the decorated property. For example: paragraphs -> paragraphsChanged, like the following example:

    ### Change handler callback reminders - The change handler will be called upon the initial rendering, and after every mutation afterwards while the custom element is still active {% %}

    @slotted usage

    The @slotted decorator can be used in multiple forms:

    Usage
    Meaning

    @slotted() prop

    Use default options, observe projection on the default slot, and select all elements

    @slotted('div') prop

    Observe projection on the default slot, and select only div elements

    @slotted('div', 'footer') prop

    Observe projection on the footer slot and select only div elements

    @slotted('*')

    Observe projection on the default slot, and select all nodes, including text

    @slotted('div', '*')

    Observe projection on all slots, and select only div elements

    Note: the `@slotted` decorator won't be notified if the children of a slotted node change — only if you change (e.g. add or delete) the actual nodes themselves. {% %}

    With slotchange binding

    The standard <slot> element dispatches slotchange events for application to react to changes in the projection. This behavior is also supported with <au-slot>. The different are with <slot>, it's an event while for <au-slot>, it's a callback as there's no host to dispatch an event, for <au-slot> is a containerless element.

    The callback will be passed 2 parameters:

    name
    type
    description

    name

    string

    the name of the slot calling the change callback

    nodes

    Node[]

    the list of the latest nodes that belongs to the slot calling the change callback

    An example of using slotchange behavior may look like the following:

    slotchange callback reminders

    • The callback will not be called upon the initial rendering, it's only called when there's a mutation after the initial rendering.

    • The callback pass to slotchange of <au-slot> will be call with an undefined this, so you should either give it a lambda expression, or a function like the example above.

    • The nodes passed to the 2nd parameter of the slotchange callback will always be the latest list of nodes.

    • the slotchange callback doesn't fire if the children of a slotted node change — only if you change (e.g. add or delete) the actual nodes themselves.

    <au-slot> (referenced below)
    <au-slot>dfb</au-slot>
    <au-slot name="s1">s1fb</au-slot>
    <au-slot name="s2">s2fb</au-slot>
    import { IAuSlotsInfo } from '@aurelia/runtime-html';
    
    class MyElement {
      public constructor(
        @IAuSlotsInfo public readonly slotInfo: IAuSlotsInfo,
      ) {
        console.log(slotInfo.projectedSlots);
      }
    }
    <!-- my_element_instance_1 -->
    <my-element>
      <div au-slot="default">dp</div>
      <div au-slot="s1">s1p</div>
    </my-element>
    <!-- my_element_instance_2 -->
    <my-element></my-element>
    <my-element>
      <div au-slot="s1">${message}</div>
    </my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        <div>outer</div>
      </my-element>
    -->
    export class MyApp {
      public readonly message: string = 'outer';
    }
    <au-slot name="s1">${message}</au-slot>
    export class MyElement {
      public readonly message: string = 'inner';
    }
    <my-element>
      <foo-bar au-slot="s1" foo.bind="message"></foo-bar>
    </my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        <foo-bar>outer</foo-bar>
      </my-element>
    -->
    export class MyApp {
      public readonly message: string = 'outer';
    }
    <au-slot name="s1">${message}</au-slot>
    export class MyElement {
      public readonly message: string = 'inner';
    }
    ${foo}
    export class FooBar {
      @bindable public foo: string;
    }
    <my-element></my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        inner
      </my-element>
    -->
    export class MyApp {
      public readonly message: string = 'outer';
    }
    <au-slot name="s1">${message}</au-slot>
    export class MyElement {
      public readonly message: string = 'inner';
    }
    <my-element></my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        <foo-bar>inner</foo-bar>
      </my-element>
    -->
    export class MyApp {
      public readonly message: string = 'outer';
    }
    <au-slot name="s1">
      <foo-bar foo.bind="message"></foo-bar>
    </au-slot>
    export class MyElement {
      public readonly message: string = 'inner';
    }
    ${foo}
    export class FooBar {
      @bindable public foo: string;
    }
    <my-element>
      <div au-slot="s1">${$host.message}</div>
      <div au-slot="s2">${message}</div>
    </my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        <div>inner</div>
        <div>outer</div>
      </my-element>
    -->
    export class MyApp {
      public readonly message: string = 'outer';
    }
    <au-slot name="s1"></au-slot>
    <au-slot name="s2"></au-slot>
    export class MyElement {
      public readonly message: string = 'inner';
    }
    <my-element>
      <foo-bar au-slot="s1" foo.bind="$host.message"></foo-bar>
    </my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        <foo-bar>inner</foo-bar>
      </my-element>
    -->
    export class MyApp {
      public readonly message: string = 'outer';
    }
    <au-slot name="s1"></au-slot>
    export class MyElement {
      public readonly message: string = 'inner';
    }
    ${foo}
    export class FooBar {
      @bindable public foo: string;
    }
    <template as-custom-element="my-element">
      <bindable property="people"></bindable>
      <au-slot name="grid">
        <au-slot name="header">
          <h4>First Name</h4>
          <h4>Last Name</h4>
        </au-slot>
        <template repeat.for="person of people">
          <au-slot name="content" expose.bind="{ person, $event, $odd, $index }">
            <div>${person.firstName}</div>
            <div>${person.lastName}</div>
          </au-slot>
        </template>
      </au-slot>
    </template>
    
    <my-element people.bind="people">
      <template au-slot="header">
        <h4>Meta</h4>
        <h4>Surname</h4>
        <h4>Given name</h4>
      </template>
      <template au-slot="content">
        <div>${$host.$index}-${$host.$even}-${$host.$odd}</div>
        <div>${$host.person.lastName}</div>
        <div>${$host.person.firstName}</div>
      </template>
    </my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        <h4>Meta</h4>           <h4>Surname</h4>      <h4>Given name</h4>
    
        <div>0-true-false</div> <div>Doe</div>        <div>John</div>
        <div>1-false-true</div> <div>Mustermann</div> <div>Max</div>
      </my-element>
    -->
    export class MyApp {
      public readonly people: Person[] = [
        new Person('John', 'Doe'),
        new Person('Max', 'Mustermann'),
      ];
    }
    
    class Person {
      public constructor(
        public firstName: string,
        public lastName: string,
      ) { }
    }
    <div class="modal">
        <div class="modal-inner">
            <slot></slot>
        </div>
    </div>
    <au-modal>
        <div slot>
            <p>Modal content inside of the modal</p>
        </div>
    </au-modal>
    <div class="modal">
        <div class="modal-inner">
            <slot name="content"></slot>
        </div>
    </div>
    <au-modal>
        <div slot="name">
            <p>Modal content inside of the modal</p>
        </div>
    </au-modal>
    <div class="modal">
        <button type="button" data-action="close" class="close" aria-label="Close" click.trigger="close()" ><span aria-hidden="true">&times;</span></button>
        <div class="modal-inner">
            <slot>This is the default content shown if the user does not supply anything.</slot>
        </div>
    </div>
    my-app.html
    <slot slotchange.trigger="handleSlotChange($event.target.assignedNodes())"></slot>
    my-app.ts overflow=
    class MyApp {
      handleSlotChange(nodes: Node[]) {
        console.log('new nodes are:', nodes);
      }
    }
    my-details.ts
    export class MyDetails {
      @children('div') divs
    }
    <my-details>
      <div>@children decorator is a good way to listen to node changes without having to deal with boilerplate yourself</div>
    </my-details>
    my-element.html
    static content
    <au-slot>fallback content for default slot.</au-slot>
    <au-slot name="s1">fallback content for s1 slot.</au-slot>
    my-app.html
    <!-- Usage without projection -->
    <my-element></my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        static content
        fallback content for default slot.
        fallback content for s1 slot.
      </my-element>
    -->
    
    <!-- Usage with projection -->
    <my-element>
      <div>d</div>        <!-- using `au-slot="default"` explicitly also works. -->
      <div au-slot="s1">p1</div>
    </my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        static content
        <div>d</div>
        <div>p1</div>
      </my-element>
    -->
    
    <my-element>
      <template au-slot="s1">p1</template>
    </my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        static content
        fallback content for default slot.
        p1
      </my-element>
    -->
    my-app.html
    <template as-custom-element="my-element">
      <au-slot>dfb</au-slot>
    </template>
    
    
    <my-element><div au-slot>projection</div></my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        <div>projection</div>
      </my-element>
    -->
    my-app.html
    <!-- Do NOT work. -->
    
    <div au-slot></div>
    
    <template><div au-slot></div></template>
    
    <my-element>
      <div>
        <div au-slot></div>
      </div>
    <my-element>
    // my_element_instance_1
    ['default', 's1']
    
    // my_element_instance_2
    []
    my-element.html
    <au-slot name="s1">s1</au-slot>
    <au-slot name="s2">s2</au-slot>
    my-app.html
    <my-element>
      <div au-slot="s2">p20</div>
      <div au-slot="s1">p11</div>
      <div au-slot="s2">p21</div>
      <div au-slot="s1">p12</div>
    </my-element>
    <!-- Rendered (simplified): -->
    <!--
      <my-element>
        <div>p11</div>
        <div>p12</div>
    
        <div>p20</div>
        <div>p21</div>
      </my-element>
    -->
    my-element.html
    <au-slot name="header"></au-slot>
    <au-slot name="content"></au-slot>
    my-app.html
    <my-tabs>
      <h3 au-slot="header">Tab1</h3>
      <div au-slot="content">Tab1 content</div>
    
      <h3 au-slot="header">Tab2</h3>
      <div au-slot="content">Tab2 content</div>
    
      <!--...-->
    </my-tabs>
    person-card.html
    <let details-shown.bind="false"></let>
    <au-slot name="name"></au-slot>
    <button click.delegate="detailsShown=!detailsShown">Toggle details</button>
    <div if.bind="detailsShown">
      <au-slot name="name"></au-slot>
      <au-slot name="role"></au-slot>
      <au-slot name="details"></au-slot>
    </div>
    my-app.html
    <person-card>
      <span au-slot="name"> John Doe </span>
      <span au-slot="role"> Role1 </span>
      <span au-slot="details"> Lorem ipsum </span>
    </person-card>
    app.html
    <my-summary>
      <p>This is a demo of the @slotted decorator</p>
      <p>It can get all the "p" elements with a simple decorator</p>
    </my-summary>
    my-summary.html
    <p>Heading text</p>
    <div>
      <au-slot></au-slot>
    </div>
    my-summary.ts
    import { slotted } from 'aurelia';
    
    export class MySummaryElement {
      @slotted('p') paragraphs // assert paragraphs.length === 2
    }
    my-summary.ts
    import { slotted } from 'aurelia';
    
    export class MySummaryElement {
      @slotted('p') paragraphs // assert paragraphs.length === 2
    
      paragraphsChanged(ps: HTMLParagraphElement[]) {
        // do things
      }
    }
    my-summary.html
    <p>Heading text</p>
    <div>
      <au-slot></au-slot>
    </div>
    app.html
    <my-summary>
      <p>This is a demo of the @slotted decorator</p>
      <p>It can get all the "p" elements with a simple decorator</p>
    </my-summary>
    app.html
    <my-summary>
      <p>This is a demo of the @slotted decorator</p>
      <p if.bind="describeMore">It can get all the "p" elements with a simple decorator</p>
    </my-summary>
    my-summary.html
    <p>Heading text</p>
    <div>
      <au-slot slotchange.bind="onContentChange"></au-slot>
      <au-slot slotchange.bind="(name, nodes) => doSomething(name, nodes)"></au-slot>
    </div>
    my-summary.ts
    import { slotted } from 'aurelia';
    
    export class MySummaryElement {
      @slotted('p') paragraphs // assert paragraphs.length === 1
    
      onContentChange = (name: string, nodes: Node[]) => {
        // handle the new set of nodes here
        console.assert(this === undefined);
      }
    
      doSomething(name: string, nodes: Node[]) {
        console.assert(this instanceof MySummaryElement);
      }
    }

    @slotted({ query: 'div' })

    Observe projection on the default slot, and select only div elements

    @slotted({ slotName: 'footer' })

    Observe projection on footer slot, and select all elements

    @slotted({ callback: 'nodeChanged' })

    Observe projection on default slot, and select all elements, and call nodeChanged method on projection change

    Configuring routes

    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.

    Route configuration basics

    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 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 gets interesting. Another way of creating aliases is to use the 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 parameters

    The 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

    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 .

    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

    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.

    Wildcard parameters

    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.

    Setting the title

    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 when customizing the router configuration.

    Note that, instead of a string, a function can also be used for title to lazily set the title.

    Redirect to another path

    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.

    Fallback: redirecting the unknown path

    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 . 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 ). However, every child can override the fallback as needed. The following example demonstrate this. The root has two 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.

    Case sensitive routes

    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.

    Advanced route configuration options

    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 hrefs in the view when using the or using the . 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

    Specifying component

    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.

    Using inline import()

    Components can be configured using the 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.

    Using the name

    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.

    Using a function returning the class

    Components can be configured using a function that returns a class.

    You can see this configuration in action below.

    Using custom element definition

    Components can be configured using custom element definition.

    You can see this configuration in action below.

    Using custom element instance

    Components can be configured using custom element instance.

    You can see this configuration in action below.

    Using classes as routes

    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 .

    Distributed routing configurations

    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.

    Navigating

    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.

    Using the href custom attribute

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

  • getting started
    path parameters
    redirectTo
    routing hooks
    custom buildTitle function
    route-id
    documentation
    sibling viewports
    load custom attribute
    Router#load API
    import()
    this section
    documentation
    You 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.

    Using route-id

    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.

    Targeting viewports

    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.

    Navigate in current and ancestor routing context

    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.

    Bypassing the href custom attribute

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

    Using the load custom attribute

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

    Binding the route-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.

    Using the route view-model class as 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.

    Customize the routing context

    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 status

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

    "active" CSS class

    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.

    Using the Router API

    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.

    Using string instructions

    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.

    Using non-string routing instructions

    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.

    Using navigation options

    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.

    Redirection and unknown paths

    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.

    • Redirection documentation

    • Fallback using the route configuration

    • Fallback using the viewport attribute

    router-lite - getting started - StackBlitzStackBlitz
    import { route } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    
    @route({
      title: 'Aurelia',
      routes: [
        {
          path: ['', 'home'],
          component: Home,
        },
        {
          path: 'about',
          component: About,
        },
      ],
    })
    export class MyApp {}
    import { Routeable } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    
    export class MyApp {
      // corresponds to the `title` property in the options object used in the @route decorator.
      static title: string = 'Aurelia';
    
      // corresponds to the `routes` property in the options object used in the @route decorator.
      static routes: Routeable[] = [
        {
          path: ['', 'home'],
          component: Home,
        },
        {
          path: 'about',
          component: About,
        },
      ];
    }
    import { IRouteConfig, RouteNode } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    
    export class MyApp {
      public getRouteConfig(_parentConfig: IRouteConfig | null, _routeNode: RouteNode | null): IRouteConfig {
        return {
          routes: [
            {
              path: ['', 'home'],
              component: Home,
              title: 'Home',
            },
            {
              path: 'about',
              component: About,
              title: 'About',
            },
          ],
        };
      }
    }
    import { IRouteConfig, RouteNode } from '@aurelia/router-lite';
    
    export class MyApp {
      public getRouteConfig(_parentConfig: IRouteConfig | null, _routeNode: RouteNode | null): IRouteConfig {
        return {
          routes: [
            {
              path: ['', 'home'],
              component: await import('./home').then((x) => x.Home),
              title: 'Home',
            },
            {
              path: 'about',
              component: await import('./about').then((x) => x.About),
              title: 'About',
            },
          ],
        };
      }
    }
    import { route } from '@aurelia/router-lite';
    import { Product } from './product';
    
    @route({
      routes: [
        {
          path: 'products/:id',
          component: Product,
        },
      ],
    })
    export class MyApp {}
    import { IRouteViewModel, Params } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    import template from './product.html';
    
    @customElement({ name: 'pro-duct', template })
    export class Product implements IRouteViewModel {
      public canLoad(params: Params): boolean {
        console.log(params.id);
        return true;
      }
    }
    import { route } from '@aurelia/router-lite';
    import { Product } from './product';
    
    @route({
      routes: [
        {
          path: 'product/:id?',
          component: Product,
        },
      ],
    })
    export class MyApp {}
    <li>
      <a href="../product">Random product</a>
    </li>
    public canLoad(params: Params): boolean {
      let id = Number(params.id);
      if (Number.isNaN(id)) {
        id = Math.ceil(Math.random() * 30);
      }
    
      this.promise = this.productService.get(id);
      return true;
    }
    import { route } from '@aurelia/router-lite';
    import { Product } from './product';
    
    @route({
      routes: [
        {
          id: 'foo',
          path: ['product/:id', 'product/:id/*rest'],
          component: Product,
        },
      ],
    })
    export class MyApp {}
    public canLoad(params: Params): boolean {
      const id = Number(params.id);
      this.promise = this.productService.get(id);
      this.showImage = params.rest == 'image';
      return true;
    }
    import { route, IRouteViewModel } from '@aurelia/router-lite';
    @route({
        title: 'Aurelia', // <-- this is the base title
        routes: [
          {
            path: ['', 'home'],
            component: import('./components/home-page'),
            title: 'Home',
          }
        ]
    })
    export class MyApp implements IRouteViewModel {}
    import { IRouteViewModel, Routeable } from "aurelia";
    export class MyApp implements IRouteViewModel {
      static title: string = 'Aurelia'; // <-- this is the base title
      static routes: Routeable[] = [
        {
          path: ['', 'home'],
          component: import('./components/home-page'),
          title: 'Home',
        }
      ];
    }
    @route({
      routes: [
        { path: '', redirectTo: 'home' },
        { path: 'about-us', redirectTo: 'about' },
        {
          path: 'home',
          component: Home,
        },
        {
          path: 'about',
          component: About,
        },
      ],
    })
    export class MyApp {}
    @route({
      routes: [
        { path: 'foo', redirectTo: 'home' },
        { path: 'bar', redirectTo: 'about' },
        { path: 'fizz', redirectTo: 'about-us' },
        {
          path: ['', 'home'],
          component: Home,
          title: 'Home',
        },
        {
          path: ['about', 'about-us'],
          component: About,
        },
      ],
    })
    export class MyApp {}
    import { route } from '@aurelia/router-lite';
    import { About } from './about';
    
    @route({
      routes: [
        { path: 'about-us/:foo/:bar', redirectTo: 'about/:bar/:foo' },
        {
          path: 'about/:p1?/:p2?',
          component: About,
          title: 'About',
        },
      ],
    })
    export class MyApp {}
    import { route } from '@aurelia/router-lite';
    import template from './my-app.html';
    import { Home } from './home';
    import { About } from './about';
    import { NotFound } from './not-found';
    
    @route({
      routes: [
        {
          path: ['', 'home'],
          component: Home,
          title: 'Home',
        },
        {
          path: 'about',
          component: About,
          title: 'About',
        },
        {
          path: 'notfound',
          component: NotFound,
          title: 'Not found',
        },
      ],
      fallback: 'notfound', // <-- fallback configuration
    })
    export class MyApp {}
    fallback(viewportInstruction: ViewportInstruction, routeNode: RouteNode, context: IRouteContext): string;
    import { customElement } from '@aurelia/runtime-html';
    import {
      IRouteContext,
      ITypedNavigationInstruction_string,
      route,
      RouteNode,
      ViewportInstruction,
    } from '@aurelia/router-lite';
    
    @customElement({ name: 'ce-a', template: 'a' })
    class A {}
    
    @customElement({ name: 'n-f-1', template: 'nf1' })
    class NF1 {}
    
    @customElement({ name: 'n-f-2', template: 'nf2' })
    class NF2 {}
    
    @route({
      routes: [
        { id: 'r1', path: ['', 'a'], component: A },
        { id: 'r2', path: ['nf1'], component: NF1 },
        { id: 'r3', path: ['nf2'], component: NF2 },
      ],
      fallback(vi: ViewportInstruction, _rn: RouteNode, _ctx: IRouteContext): string {
        return (vi.component as ITypedNavigationInstruction_string).value === 'foo' ? 'r2' : 'r3';
      },
    })
    @customElement({
      name: 'my-app',
      template: `
      <nav>
      <a href="a">A</a>
      <a href="foo">Foo</a>
      <a href="bar">Bar</a>
    </nav>
    
    <au-viewport></au-viewport>`
    })
    export class MyApp {}
    import { route } from '@aurelia/router-lite';
    import { About } from './about';
    
    @route({
      routes: [
        {
          path: 'AbOuT',
          component: About,
          caseSensitive: true,
        },
      ],
    })
    export class MyApp {}
      import { customElement } from '@aurelia/runtime-html';
      import { route } from '@aurelia/router-lite';
      import template from './my-app.html';
    - import { About } from './about';
    - import { Home } from './home';
    
      @route({
        routes: [
          {
            path: ['', 'home'],
    -       component: Home,
    +       component: import('./home'),
            title: 'Home',
          },
          {
            path: 'about',
    -       component: About,
    +       component: import('./about'),
            title: 'About',
          },
        ],
      })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
      import { customElement } from '@aurelia/runtime-html';
      import { route } from '@aurelia/router-lite';
      import template from './my-app.html';
    - import { About } from './about';
    - import { Home } from './home';
    
      @route({
        routes: [
          {
            path: ['', 'home'],
    -       component: Home,
    +       component: 'ho-me', // <-- assuming that Home component has the name 'ho-me'
            title: 'Home',
          },
          {
            path: 'about',
    -       component: About,
    +       component: 'ab-out', // <-- assuming that About component has the name 'ab-out'
            title: 'About',
          },
        ],
      })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
    // main.ts
    import { RouterConfiguration } from '@aurelia/router-lite';
    import { Aurelia, StandardConfiguration } from '@aurelia/runtime-html';
    import { About } from './about';
    import { Home } from './home';
    import { MyApp as component } from './my-app';
    
    (async function () {
      const host = document.querySelector<HTMLElement>('app');
      const au = new Aurelia();
      au.register(
        StandardConfiguration,
        RouterConfiguration,
    
        // component registrations
        Home,
        About,
      );
      au.app({ host, component });
      await au.start();
    })().catch(console.error);
      import { customElement } from '@aurelia/runtime-html';
      import { route } from '@aurelia/router-lite';
      import template from './my-app.html';
    - import { About } from './about';
    - import { Home } from './home';
    
      @route({
        routes: [
          {
            path: ['', 'home'],
    -       component: Home,
    +       component: () => {
    +         @customElement({ name: 'ho-me', template: '<h1>${message}</h1>' })
    +         class Home {
    +           private readonly message: string = 'Welcome to Aurelia2 router-lite!';
    +         }
    +         return Home;
    +       },
            title: 'Home',
          },
          {
            path: 'about',
    -       component: About,
    +       component: () => {
    +         @customElement({ name: 'ab-out', template: '<h1>${message}</h1>' })
    +         class About {
    +           private readonly message = 'Aurelia2 router-lite is simple';
    +         }
    +         return About;
    +       },
            title: 'About',
          },
        ],
      })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
      import { customElement } from '@aurelia/runtime-html';
      import { route } from '@aurelia/router-lite';
      import template from './my-app.html';
    - import { About } from './about';
    - import { Home } from './home';
    
    + class Home {
    +   private readonly message: string = 'Welcome to Aurelia2 router-lite!';
    + }
    + const homeDefn = CustomElementDefinition.create(
    +   { name: 'ho-me', template: '<h1>${message}</h1>' },
    +   Home
    + );
    + CustomElement.define(homeDefn, Home);
    +
    + class About {
    +   private readonly message = 'Aurelia2 router-lite is simple';
    + }
    + const aboutDefn = CustomElementDefinition.create(
    +   { name: 'ab-out', template: '<h1>${message}</h1>' },
    +   About
    + );
    + CustomElement.define(aboutDefn, About);
    
      @route({
        routes: [
          {
            path: ['', 'home'],
    -       component: Home,
    +       component: homeDefn,
            title: 'Home',
          },
          {
            path: 'about',
    -       component: About,
    +       component: aboutDefn,
            title: 'About',
          },
        ],
      })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
      import { customElement } from '@aurelia/runtime-html';
      import { route } from '@aurelia/router-lite';
      import template from './my-app.html';
    - import { About } from './about';
    - import { Home } from './home';
    
    + @customElement({ name: 'ho-me', template: '<h1>${message}</h1>' })
    + class Home {
    +   private readonly message: string = 'Welcome to Aurelia2 router-lite!';
    + }
    +
    + @customElement({ name: 'ab-out', template: '<h1>${message}</h1>' })
    + class About {
    +   private readonly message = 'Aurelia2 router-lite is simple';
    + }
    
      @route({
        routes: [
          {
            path: ['', 'home'],
    -       component: Home,
    +       component: new Home(),
            title: 'Home',
          },
          {
            path: 'about',
    -       component: About,
    +       component: new About(),
            title: 'About',
          },
        ],
      })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
    @customElement({ name: 'c-1', template: 'c1', aliases: ['c-a', 'c-one'] })
    class C1 { }
    
    @customElement({ name: 'c-2', template: 'c2', aliases: ['c-b', 'c-two'] })
    class C2 { }
    
    @route({
      routes: [C1, C2]
    })
    @customElement({ name: 'ro-ot', template: '<au-viewport></au-viewport>' })
    class Root { }
    import { customElement } from '@aurelia/runtime-html';
    import { route } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    
    @route({
      routes: [Home, About],
    })
    @customElement({
      name: 'my-app',
      template: `
    <nav>
      <a href="home">Home</a>
      <a href="about">About</a>
    </nav>
    
    <au-viewport></au-viewport>
    `
    })
    export class MyApp {}
    
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @route(['', 'home'])
    @customElement({ name: 'ho-me', template: '<h1>${message}</h1>' })
    export class Home {
      private readonly message: string = 'Welcome to Aurelia2 router-lite!';
    }
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @route('about')
    @customElement({ name: 'ab-out', template: '<h1>${message}</h1>' })
    export class About {
      private readonly message = 'Aurelia2 router-lite is simple';
    }
    import { customElement } from '@aurelia/runtime-html';
    import { route } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    
    @route({
      routes: [Home, About],
    })
    @customElement({
      name: 'my-app',
      template: `
    <nav>
      <a href="home">Home</a>
      <a href="about">About</a>
    </nav>
    
    <au-viewport></au-viewport>
    `
    })
    export class MyApp {}
    
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @route({ path: ['', 'home'], title: 'Home' })
    @customElement({ name: 'ho-me', template: '<h1>${message}</h1>' })
    export class Home {
      private readonly message: string = 'Welcome to Aurelia2 router-lite!';
    }
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @route({ path: 'about', title: 'About' })
    @customElement({ name: 'ab-out', template: '<h1>${message}</h1>' })
    export class About {
      private readonly message = 'Aurelia2 router-lite is simple';
    }
    import { customElement } from '@aurelia/runtime-html';
    import { route } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    
    @route({
      routes: [
        Home,
        { path: 'about-us', component: About, title: 'About us' }
      ],
    })
    @customElement({
      name: 'my-app',
      template: `
    <nav>
      <a href="home">Home</a>
      <a href="about-us">About</a>
    </nav>
    
    <au-viewport></au-viewport>
    `
    })
    export class MyApp {}
    
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @route({ path: ['', 'home'], title: 'Home' })
    @customElement({ name: 'ho-me', template: '<h1>${message}</h1>' })
    export class Home {
      private readonly message: string = 'Welcome to Aurelia2 router-lite!';
    }
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @route({ path: 'about', title: 'About' })
    @customElement({ name: 'ab-out', template: '<h1>${message}</h1>' })
    export class About {
      private readonly message = 'Aurelia2 router-lite is simple';
    }
    import { customElement } from '@aurelia/runtime-html';
    import {
      IRouteConfig,
      IRouteViewModel,
      route,
      RouteNode,
    } from '@aurelia/router-lite';
    import template from './my-app.html';
    import { Home } from './home';
    import { About } from './about';
    
    @route({ title: 'Aurelia2' })
    @customElement({
      name: 'my-app',
      template: `
    <nav>
      <a href="home">Home</a>
      <a href="about">About</a>
    </nav>
    
    <au-viewport></au-viewport>
    `
    })
    export class MyApp implements IRouteViewModel {
      public getRouteConfig?(
        parentConfig: IRouteConfig | null,
        routeNode: RouteNode | null
      ): IRouteConfig {
        return {
          routes: [Home, About],
        };
      }
    }
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @route({ path: ['', 'home'], title: 'Home' })
    @customElement({ name: 'ho-me', template: '<h1>${message}</h1>' })
    export class Home {
      private readonly message: string = 'Welcome to Aurelia2 router-lite!';
    }
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @route({ path: 'about', title: 'About' })
    @customElement({ name: 'ab-out', template: '<h1>${message}</h1>' })
    export class About {
      private readonly message = 'Aurelia2 router-lite is simple';
    }
    <nav>
      <a href="home">Home</a>
      <a href="about">About</a>
    </nav>
    import { route } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    
    @route({
      routes: [
        {
          path: ['', 'home'],
          component: Home,
        },
        {
          path: 'about',
          component: About,
        },
      ],
    })
    export class MyApp {}
    <nav>
      <a href="home">Home</a>
      <a href="about">About</a>
      <a href="about/42">About/42</a>
    </nav>
    import { route } from '@aurelia/router-lite';
    import { Home } from './home';
    import { About } from './about';
    
    @route({
      routes: [
        {
          path: ['', 'home'],
          component: Home,
        },
        {
          path: ['about/:id?'],
          component: About,
        },
      ],
    })
    export class MyApp {}
    import { route } from '@aurelia/router-lite';
    import { ChildOne } from './child1';
    import { ChildTwo } from './child2';
    
    @route({
      routes: [
        {
          id: 'r1',
          path: ['', 'c1'],
          component: ChildOne,
        },
        {
          id: 'r2',
          path: 'c2',
          component: ChildTwo,
        },
      ],
    })
    export class MyApp {}
    <nav>
      <a href="r1">C1</a>
      <a href="r2">C2</a>
    </nav>
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @customElement({ name: 'gc-11', template: 'gc11' })
    class GrandChildOneOne {}
    
    @customElement({ name: 'gc-12', template: 'gc12' })
    class GrandChildOneTwo {}
    
    @route({
      routes: [
        { id: 'r1', path: ['', 'gc11'], component: GrandChildOneOne },
        { id: 'r2', path: 'gc12', component: GrandChildOneTwo },
      ],
    })
    @customElement({
      name: 'c-one',
      template: `c1 <br>
      <nav>
        <a href="r1">gc11</a>
        <a href="r2">gc12</a>
      </nav>
      <br>
      <au-viewport></au-viewport>`,
    })
    export class ChildOne {}
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @customElement({ name: 'gc-21', template: 'gc21' })
    class GrandChildTwoOne {}
    
    @customElement({ name: 'gc-22', template: 'gc22' })
    class GrandChildTwoTwo {}
    
    @route({
      routes: [
        { id: 'r1', path: ['', 'gc21'], component: GrandChildTwoOne },
        { id: 'r2', path: 'gc22', component: GrandChildTwoTwo },
      ],
    })
    @customElement({
      name: 'c-two',
      template: `c2 <br>
      <nav>
        <a href="r1">gc21</a>
        <a href="r2">gc22</a>
      </nav>
      <br>
      <au-viewport></au-viewport>`,
    })
    export class ChildTwo {}
    import { route } from '@aurelia/router-lite';
    import { ChildOne } from './child1';
    import { ChildTwo } from './child2';
    import { NotFound } from './not-found';
    
    @route({
      routes: [
        {
          path: ['', 'c1'],
          component: ChildOne,
        },
        {
          path: 'c2',
          component: ChildTwo,
        },
        {
          path: 'not-found',
          component: NotFound,
        },
      ],
      fallback: 'not-found',
    })
    @customElement({ name: 'my-app', template })
    export class MyApp {}
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @customElement({ name: 'gc-11', template: 'gc11' })
    class GrandChildOneOne {}
    
    @customElement({ name: 'gc-12', template: 'gc12' })
    class GrandChildOneTwo {}
    
    @route({
      routes: [
        { id: 'gc11', path: ['', 'gc11'], component: GrandChildOneOne },
        { id: 'gc12', path: 'gc12', component: GrandChildOneTwo },
      ],
    })
    @customElement({
      name: 'c-one',
      template: `c1 <br>
      <nav>
        <a href="gc11">gc11</a>
        <a href="gc12">gc12</a>
        <a href="c2">c2 (doesn't work)</a>
        <a href="../c2">../c2 (works)</a>
      </nav>
      <br>
      <au-viewport></au-viewport>`,
    })
    export class ChildOne {}
    import { route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @customElement({ name: 'gc-21', template: 'gc21' })
    class GrandChildTwoOne {}
    
    @customElement({ name: 'gc-22', template: 'gc22' })
    class GrandChildTwoTwo {}
    
    @route({
      routes: [
        { id: 'gc21', path: ['', 'gc21'], component: GrandChildTwoOne },
        { id: 'gc22', path: 'gc22', component: GrandChildTwoTwo },
      ],
    })
    @customElement({
      name: 'c-two',
      template: `c2 <br>
      <nav>
        <a href="gc21">gc21</a>
        <a href="gc22">gc22</a>
        <a href="c1">c1 (doesn't work)</a>
        <a href="../c1">../c1 (works)</a>
      </nav>
      <br>
      <au-viewport></au-viewport>`,
    })
    export class ChildTwo {}
    import { route } from '@aurelia/router-lite';
    import { ChildTwo } from './child2';
    
    @route({
      routes: [
        {
          id: 'r2',
          path: ['c2/:p1/foo/:p2?', 'c2/:p1/foo/:p2/bar/:p3'],
          component: ChildTwo,
        },
      ],
    })
    export class MyApp {}
    <!-- constructed path: /c2/1/foo/ -->
    <a load="route: r2; params.bind: {p1: 1};">C2 {p1: 1}</a>
    
    <!-- constructed path: /c2/2/foo/3 -->
    <a load="route: r2; params.bind: {p1: 2, p2: 3};">C2 {p1: 2, p2: 3}</a>
    
    <!-- constructed path: /c2/4/foo/?p3=5 -->
    <a load="route: r2; params.bind: {p1: 4, p3: 5};">C2 {p1: 4, p3: 5}</a>
    
    <!-- constructed path: /c2/6/foo/7/bar/8 -->
    <a load="route: r2; params.bind: {p1: 6, p2: 7, p3: 8};">C2 {p1: 6, p2: 7, p3: 8}</a>
    
    <!-- constructed path: /c2/9/foo/10/bar/11?p4=awesome&p5=possum -->
    <a load="route: r2; params.bind: {p1: 9, p2: 10, p3: 11, p4: 'awesome', p5: 'possum'};">C2 {p1: 9, p2: 10, p3: 11, p4: 'awesome', p5: 'possum'}</a>
    {path1}[@{viewport-name}][+{path2}[@{sibling-viewport-name}]]
    <!-- Load the products' list in the first viewport and the details in the second viewport -->
    <a href="products+details/${id}">Load products+details/${id}</a>
    
    <!-- Load the details in the first viewport and the products' list in the second viewport -->
    <a href="details/${id}+products">Load details/${id}+products</a>
    
    <!-- Specifically target the named viewports -->
    <a href="products@list+details/${id}@details">Load products@list+details/${id}@details</a>
    <a href="products@details+details/${id}@list">Load products@details+details/${id}@list</a>
    
    <!-- Load only the details in the specific named viewport -->
    <a href="details/${id}@details">Load details/${id}@details</a>
     <a href="c2">c2 (doesn't work)</a>
    <a href="../c2">../c2 (works)</a>
    <!-- my-app.html -->
    <!-- instructions pointing to individual routes -->
    <a load="c1">C1</a>
    <a load="c2">C2</a>
    <!-- instructions involving sibling viewports -->
    <a load="c1+c2">C1+C2</a>
    <a load="c1@vp2+c2@vp1">C1@vp2+C2@vp1</a>
    
    <!-- child1 -->
    <!-- instruction pointing to parent routing context -->
    <a load="../c2">../c2</a>
    // my-app.ts
    import { ChildOne } from './child1';
    import { ChildTwo } from './child2';
    
    export class MyApp {
      private readonly child1: typeof ChildOne = ChildOne;
      private readonly child2: typeof ChildTwo = ChildTwo;
    }
    <!-- my-app.html -->
    <a load="route.bind: child1">C1</a>
    <a load="route.bind: child2; params.bind: {p1: 1};">C2 {p1: 1}</a>
    <a load="route.bind: child2; params.bind: {p1: 2, p2: 3};">C2 {p1: 2, p2: 3}</a>
    <a load="route.bind: child2; params.bind: {p1: 4, p3: 5};">C2 {p1: 4, p3: 5}</a>
    <a load="route.bind: child2; params.bind: {p1: 6, p2: 7, p3: 8};">C2 {p1: 6, p2: 7, p3: 8}</a>
    <a load="route.bind: child2; params.bind: {p1: 9, p2: 10, p3: 11, p4: 'awesome', p5: 'possum'};">C2 {p1: 9, p2: 10, p3: 11, p4: 'awesome', p5: 'possum'}</a>
    export class ChildOne {
      private readonly parentCtx: IRouteContext;
      public constructor(@IRouteContext ctx: IRouteContext) {
        this.parentCtx = ctx.parent;
      }
    }
    <a load="route: r2; context.bind: parentCtx">c2</a>
    <a load="route: r2; context.bind: null">Go to root c2</a>
    <a load="route: ../r2">c2</a>
    <style>
      a.active {
        font-weight: bolder;
      }
    </style>
    
    <nav>
      <a
        load="route:foo; params.bind:{id: 1}; active.bind:active1"
        active.class="active1"
        >foo/1</a
      >
      <a load="route:foo/2; active.bind:active2" active.class="active2">foo/2</a>
    </nav>
    
    <au-viewport></au-viewport>
    import { IRouter, IRouteableComponent } from '@aurelia/router-lite';
    
    export class MyComponent {
      public constructor(@IRouter private readonly router: IRouter) { }
    }
    router.load('c1')
    router.load('c2')
    router.load('c2/42')
    router.load('c1+c2')
    router.load('c1@vp2+c2@vp1')
    // in ChildOne
    router.load('c2');
    
    
    // in ChildTwo
    router.load('c1');
    // in ChildOne
    router.load('gc11', { context: this });
    
    // in ChildTwo
    router.load('gc21', { context: this });
    router.load(['c1', 'c2']);
    router.load(['c1', 'c2/21']);
    router.load(ChildOne);
    router.load([ChildOne, ChildTwo]);
    
    router.load(GrandChildOneOne, { context: this });
    import { CustomElement } from '@aurelia/runtime-html';
    
    router.load(CustomElement.getDefinition(ChildOne));
    router.load([
      CustomElement.getDefinition(ChildOne),
      CustomElement.getDefinition(ChildTwo)
    ]);
    
    router.load(
      CustomElement.getDefinition(GrandChildOneOne),
      { context: this }
    );
    router.load(() => ChildOne);
    router.load([() => ChildOne, () => ChildTwo]);
    
    router.load(() => GrandChildOneOne, { context: this });
    router.load(import('./child1'));          // uses the default or first non-default import
    router.load([
      import('./child1'),
      import('./child2').then(m => m.Child2) // selective import
    ]);
    router.load(Promise.resolve({ ChildOne }));
    // using a route-id
    router.load({ component: 'c1' });
    
    // using a class
    router.load({ component: ChildTwo });
    
    // load sibling routes
    router.load([
      // use custom element definition
      { component: CustomElement.getDefinition(ChildOne) },
      // use a function returning class
      { component: () => ChildTwo, params: { id: 42 } },
    ]);
    
    // load sibling routes with nested children and parameters etc.
    router.load([
      // using path
      {
        component: 'c1',
        children: [{ component: GrandChildOneTwo }],
        viewport: 'vp2',
      },
      // using import
      {
        component: import('./child2'),
        params: { id: 21 },
        children: [{ component: GrandChildTwoTwo }],
        viewport: 'vp1',
      },
    ]);
    router.load(Home, { title: 'Some title' });
    router.load(Home, { titleSeparator: '-' });
    // the generated URL: /home?foo=bar&fizz=buzz
    router.load(
      'home',
      {
        queryParams: {
          foo: 'bar',
          fizz: 'buzz',
        }
      }
    );
    // the generated URL: /home#foobar
    router.load(
      'home',
      {
        fragment: 'foobar'
      }
    );
    router.load('child-route', { context: this });
    import { IRouteContext, IRouter, Params, route } from '@aurelia/router-lite';
    import { customElement } from '@aurelia/runtime-html';
    
    @customElement({ name: 'gc-21', template: 'gc21' })
    class GrandChildTwoOne {}
    
    @customElement({ name: 'gc-22', template: 'gc22' })
    class GrandChildTwoTwo {}
    
    @route({
      routes: [
        { id: 'gc21', path: ['', 'gc21'], component: GrandChildTwoOne },
        { id: 'gc22', path: 'gc22', component: GrandChildTwoTwo },
      ],
    })
    @customElement({
      name: 'c-two',
      template: `c2 <br>
      id: \${id}
      <nav>
        <button click.trigger="load('gc21', true)">Go to gc21</button>
        <button click.trigger="load('gc22', true)">Go to gc22</button>
        <button click.trigger="load('c1')"        >Go to c1  </button>
      </nav>
      <br>
      <au-viewport></au-viewport>`,
    })
    export class ChildTwo {
      private id: string;
      public constructor(
        @IRouter private readonly router: IRouter,
        // injected instance of IRouteContext
        @IRouteContext private readonly context: IRouteContext
      ) {}
    
      private load(route: string, useCurrentContext: boolean = false) {
        void this.router.load(
          route,
          useCurrentContext
            ? {
                // use the injected IRouteContext as navigation context.
                context: this.context
              }
            : undefined
        );
      }
      public loading(params: Params) {
        this.id = params.id ?? 'NA';
      }
    }
    import { IRouter, route } from '@aurelia/router-lite';
    import {
      customElement,
      type ICustomElementController,
      type IHydratedCustomElementViewModel,
    } from '@aurelia/runtime-html';
    
    @customElement({ name: 'gc-11', template: 'gc11' })
    class GrandChildOneOne {}
    
    @customElement({ name: 'gc-12', template: 'gc12' })
    class GrandChildOneTwo {}
    
    @route({
      routes: [
        { id: 'gc11', path: ['', 'gc11'], component: GrandChildOneOne },
        { id: 'gc12', path: 'gc12', component: GrandChildOneTwo },
      ],
    })
    @customElement({
      name: 'c-one',
      template: `c1 <br>
      <nav>
        <button click.trigger="load('gc11', true)">Go to gc11</button>
        <button click.trigger="load('gc12', true)">Go to gc12</button>
        <button click.trigger="load('c2')"        >Go to c2  </button>
      </nav>
      <br>
      <au-viewport></au-viewport>`,
    })
    export class ChildOne implements IHydratedCustomElementViewModel {
      // set by aurelia pipeline
      public readonly $controller: ICustomElementController<this>;
      public constructor(@IRouter private readonly router: IRouter) {}
    
      private load(route: string, useCurrentContext: boolean = false) {
        void this.router.load(
          route,
          useCurrentContext
            ? {
                // use the custom element controller as navigation context
                context: this.$controller
              }
            : undefined
        );
      }
    }
    router.load('c1');
    router.load('c2');
    router.load('c3', { historyStrategy: 'replace' })
    @route({
      transitionPlan: 'replace',
      routes: [
        {
          id: 'ce1',
          path: ['ce1/:id'],
          component: CeOne,
          transitionPlan: 'invoke-lifecycles',
        },
        {
          id: 'ce2',
          path: ['ce2/:id'],
          component: CeTwo,
          transitionPlan: 'replace',
        },
      ],
    })
    @customElement({
      name: 'my-app',
      template: `
    <button click.trigger="navigate('ce1/42')">ce1/42 (default: invoke lifecycles)</button><br>
    <button click.trigger="navigate('ce1/43')">ce1/43 (default: invoke lifecycles)</button><br>
    <button click.trigger="navigate('ce1/44', 'replace')">ce1/44 (override: replace)</button><br>
    <br>
    
    <button click.trigger="navigate('ce2/42')">ce2/42 (default: replace)</button><br>
    <button click.trigger="navigate('ce2/43')">ce2/43 (default: replace)</button><br>
    <button click.trigger="navigate('ce2/44', 'invoke-lifecycles')">ce2/44 (override: invoke lifecycles)</button><br>
    
    <au-viewport></au-viewport>
    `,
    })
    export class MyApp {
      public constructor(@IRouter private readonly router: IRouter) {}
      private navigate(
        path: string,
        transitionPlan?: 'replace' | 'invoke-lifecycles'
      ) {
        void this.router.load(
          path,
          transitionPlan ? { transitionPlan } : undefined
        );
      }
    }
    Logo
    router-lite - wildcard param - StackBlitzStackBlitz
    router-lite - fallback - StackBlitzStackBlitz
    router-lite - redirect - multiple paths - StackBlitzStackBlitz
    router-lite - redirect - StackBlitzStackBlitz
    router-lite - component - ce-defn - StackBlitzStackBlitz
    router-lite - getRouteConfig hook - StackBlitzStackBlitz
    router-lite - path on vm - StackBlitzStackBlitz
    router-lite - route config on vm - StackBlitzStackBlitz
    router-lite - component - inline import - StackBlitzStackBlitz
    router-lite - fallback - using routeid - StackBlitzStackBlitz
    router-lite - async getRouteConfig hook - StackBlitzStackBlitz
    router-lite - fallback - using function - StackBlitzStackBlitz
    router-lite - required param - StackBlitzStackBlitz
    router-lite - component - ce-instance - StackBlitzStackBlitz
    router-lite - fallback - using ce name - StackBlitzStackBlitz
    router-lite - hybrid config - StackBlitzStackBlitz
    router-lite - IRouter#load - context - controller - StackBlitzStackBlitz
    router-lite - load - active - StackBlitzStackBlitz
    router-lite - bypassing href - StackBlitzStackBlitz
    router-lite - href - route-id - StackBlitzStackBlitz
    router-lite - href with bound parameter - StackBlitzStackBlitz
    router-lite - load - class-as-route - StackBlitzStackBlitz
    Logo
    Logo
    Logo
    Logo
    router-lite - redirect - parameterized - StackBlitzStackBlitz
    router-lite - case-sensitive - StackBlitzStackBlitz
    router-lite - optional param - StackBlitzStackBlitz
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    router-lite - component - ce-name - StackBlitzStackBlitz
    router-lite - component - function - StackBlitzStackBlitz
    router-lite - parent overrides comp route config - StackBlitzStackBlitz
    router-lite - transitionPlan - nav opt - StackBlitzStackBlitz
    router-lite - IRouter#load - context - IRouteContext - StackBlitzStackBlitz
    router-lite - IRouter#load - CE - StackBlitzStackBlitz
    router-lite - named-sibling-viewport - href - StackBlitzStackBlitz
    router-lite - load - nav options - title - StackBlitzStackBlitz
    router-lite - load - nav options - query - StackBlitzStackBlitz
    router-lite - load - nav options - query - StackBlitzStackBlitz
    router-lite - IRouter#load - import() - StackBlitzStackBlitz
    router-lite - IRouter#load - string-instructions - StackBlitzStackBlitz
    router-lite - IRouter#load - array-of-paths - siblings - StackBlitzStackBlitz
    router-lite - IRouter#load - component factory - StackBlitzStackBlitz
    router-lite - load - parent context - StackBlitzStackBlitz
    router-lite - load - nav options - title separator - StackBlitzStackBlitz
    router-lite - href - hierarchical - current-context - StackBlitzStackBlitz
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    router-lite - IRouter#load - CE Definition - StackBlitzStackBlitz
    router-lite - load - current context - StackBlitzStackBlitz
    router-lite - IRouter#load - viewport instruction - StackBlitzStackBlitz
    router-lite - load - string-instructions - StackBlitzStackBlitz
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    Logo
    router-lite - load - null/root context - StackBlitzStackBlitz
    router-lite - IRouter#load - string-instructions - StackBlitzStackBlitz
    Logo
    Logo
    Logo
    Logo
    router-lite - load - params - StackBlitzStackBlitz
    Logo
    Logo
    router-lite - IRouter#load - context - element - StackBlitzStackBlitz
    Logo
    router-lite - fallback - hierarchical - StackBlitzStackBlitz
    Logo
    Logo