Aurelia 1 was released in 2015. In the years that have passed, an ecosystem of user-created libraries and plugins has been created. If you are a plugin author of a v1 plugin or looking to port over a plugin to v2, this is the section you are looking for.
Before proceeding, you should read through the parent section to understand the differences between v1 and v2.
In Aurelia 2, the plugin method you called in v1 is no more. Plugins in Aurelia 2 are no different to components and other resources. You pass them to the register method inside your bootstrap code to load them.
There are numerous ways you can create Aurelia 2 plugins or port over v1 plugins. However, the easiest reference point is to look at how other plugins have been ported over to Aurelia 2.
Please keep in mind these are third-party plugins, and the Aurelia team claims no responsibility for the quality and safety of the code. Use them as a reference point for your own Aurelia 2 plugins, but use precaution.
Aurelia 2 is a complete rewrite of Aurelia that shares many of the same loved and familiar features of Aurelia 1. Understandably, in the spirit of progress, not everything is the same. In this section, we are going to guide you through what has changed and how you can migrate over your Aurelia 1 applications to Aurelia 2.
An quickest way to get an application in v1 up an running in v2 is to include the compat package. It can be done via 2 steps:
installing the compat package via
include the compat package into your app:
In v2, preventDefault is no longer called by default. This breaking change could show up in unexpected places:
click events: in v1, clicking on a button inside a form will not submit the form, while it will in v2, as the click event default behavior is no longer prevented
drag events: in v1, implementing drag/drop will have preventDefault called automatically, but in v2, they will need to be explicitly called by the application
Sometimes, if it's desirable to call preventDefault in an event binding, use prevent modifier, like the following example:
Read more about modifiers in
In v2, when trying to bind with a non-existent property, the closest boundary scope will be selected, instead of the immediate scope of the binding (v1 behavior).
observeProperty has been renamed to observeIn v1, if you happen to use .observeProperty method from bindings in your application/library, then change it to observe instead. The parameters of the signature remain the same.
sourceExpression has been renamed to astIn v1, if you happen to use .sourceExpression property from bindings in your application/library, then change it to ast instead. The type of the property remains the same.
In v1, enhance method on an Aurelia instance has the signature:
In v2, enhance method on an Aurelia instance has the signature:
Parent container and resources can be specified through this config.
In v2, in order to get a reference to the underlying component view model, use component.ref instead of view-model.ref This is to make terminologies consistent as we are moving towards component oriented terms.
The primary property of If has been renamed from condition to value. If you are using if.bind, you are not affected. If you are using the multi prop binding syntax, the template looks like this:
Change it to:
BindingEngine has been removed in v2, but can still be imported from @aurelia/compat-v1 package for ease of migration. The collectionObserver method on the compat package of BindingEngine is not the same with v1, per the follow comparison: v2
v1
.delegate command has been removed, use .trigger instead. With shadow DOM, even though .delegate works, it doesn't feel as natural as .trigger, and the performance benefits .delegate command used to give when browsers were slow adding many event listeners is no longer as big.
.call command has been removed, use lambda functions instead to create function that preserves the this
<compose> has been renamed to <au-compose>. The bindable properties of this component have also been changed:
viewModel -> component
view -> template
Read more about dynamic composition in v2 in this and .
Templates no longer need to have <template> tags as the start and ending tags. Templates can be pure HTML with enhanced Aurelia markup but <template> doesn't need to be explicitly defined.
PLATFORM.moduleName is gone. This was to address a limitation in Aurelia 1. Aurelia 2 now works well with all bundlers and does not require the addition of this code to use code splitting or tell the bundler where template code is.
Remove automatic au- prefix
Remove auto-conversion of Aurelia element -> WC element. Applications need to explicitly define this. This should make mix-matching & controlling things easier.
model remains the same
Examples migration fix:
In Aurelia 2, all bindings are passed through to the underlying custom element composition, so component.ref (view-model.ref in v1) no longer means getting a reference to the composer, but the composed view model instead.
IHttpClientIRouterv1:
<compose view.bind="...">
<compose view-model.bind="...">
v2:
<au-compose template.bind="...">
<au-compose component.bind="...">npm install @aurelia/compat-v1import { compatRegistration } from '@aurelia/compat-v1';
...
Aurelia
.register(compatRegistration, ...)
.app(...)
.start()<button click.trigger:prevent="doWork()">Submit manually</button>
<div dragstart.trigger="prepareDragdrop()" drop.trigger:prevent="onDrop()">class Aurelia {
...
enhance(elementOrConfig: Element | IEnhancementConfig): View;
}interface IAurelia {
...
enhance(enhancementConfig: IEnhancementConfig): IEnhancedView;
}<div if="condition.bind: yes"><div if="value.bind: yes">collectionObserver(collection): { subscribe: (callback: (collection, indexMap)) => { dispose(): void } }collectionObserver(collection): { subscribe: (callback: (collection, splices)) => { dispose(): void } }The first entry point of an Aurelia application is the main HTML page-loading and hosting.
aurelia-app attribute helps us to introduce our entry point, themain.tsfile, which includes the configurations of the project.
In Aurelia 2, it is a little different, you need to call your root component (<my-app> in this example) but
What happened to aurelia-app?
There is noaurelia-appin Aurelia 2. Themain.tsfile will detect via the project configuration.
All the initial settings for starting and working with an Aurelia project are done in this file.
What does PLATFORM.moduleName do?
Whenever you reference a module by string, you need to usePLATFORM.moduleName("moduleName")to wrap the bare string. PLATFORM.moduleName is designed to teachWebpackabout Aurelia's dynamic loading behavior.
What is a globalResources?
When you create a view in Aurelia, it is completely encapsulated so you mustrequirecomponents into an Aurelia view. However, certain components are used so frequently across views that it can become very tedious to import them over and over again. To solve this problem, Aurelia lets you explicitly declare certain "view resources" as global.
The root of any Aurelia application is a single component, which contains everything within the application, actually, the root component.
To import any style, component or etc, you should userequire.
Wrapping the whole HTML content viatemplateisnecessary.
Every component instance has a life-cycle that you can tap into. This makes it easy for you to perform various actions at particular times
Which life-cycle hooks are most used?
Such cases can be summarized.
A dependency injection container is a tool that can simplify the process of decomposing such a system. Oftentimes, when developers go through the work of destructuring a system, they introduce a new complexity of "re-assembling" the smaller parts again at runtime. This is what a dependency injection container can do for you, using simple declarative hints.
Writing debug output while developing is great. This is how you can do this with Aurelia.
Write an appender.
In the main(.js|.ts)
You can register LoggerConfiguration as following
Usage
How to write an appender?
How to write a sink?
| Name | Aurelia 1 | Aurelia 2 | Description | | ---- | - | - | | ref | β | β | | | view-model.ref | β | β | deprecated in v2 | | component.ref | β | β | Not in v1 |
{% hint style="info" } In v2, if an expression return a function, that function will be use as the handler for the event. V1 only evaluates the expression. {% endhint }
General
Event
Repeater
@computedFrom tells the binding system which expressions to observe. When those expressions change, the binding system will re-evaluate the property (execute the getter).
This feature is totally new for Aurelia 2.
What is a feature?
Sometimes you have a whole group of components or related functionality that collectively form a "feature". This "feature" may even be owned by a particular set of developers on your team. You want these developers to be able to manage the configuration and resources of their own feature, without interfering with the other parts of the app. For this scenario, Aurelia provides the "feature".
What is a plugin?
Similar to features, you can install 3rd party plugins. The main difference is that a "feature" is provided internally by your application, while a plugin is installed from a 3rd party source through your package manager.
What does setRoot() do?
Instantiates the root component and adds it to the DOM.
One of the best and most exciting changes has been made in this section.
What happened to PLATFORM.moduleName?
Aurelia 2 works with any bundler without limitation or specific configuration so I'm sure you guessed it, you don't need PLATFORM.moduleName("moduleName") anymore.
Is globalResourcesstill supported?
Yes, Any component or class you add to the applicationregister()will be globally accessible through DI.
How can I have a plugin?
If you are creating aplugin , then the usual practice is to export a configuration object. That can be registered in the client code. As a best practice, we recommend an alternate approach to registering each component individually in this way. Instead, create a folder where you keep all your shared components. In that folder, create a registry.ts module where you re-export your components. Then, import that registry module and pass it to the application's register method at startup.
For example:
What happened to feature?
This is conceptually similar topluginso you can do the same for internal use.
Where is the setRoot()?
The app()method is equivalent of the setRoot().
Unlike version 1, There is a convention for loading your CSS file when the name is the same as the component, just like my-app.css, so you don't need to import it manually.
To import any style, component or etc you should use import. An alternative to require in version 1. By default, the components you create aren't global. What that means is that you can't use a component within another component, unless that component has been imported.
Wrapping the whole HTML content via template is optional.
hydrated
β
β
created
created
β
binding
bind
β
bound
β
β
attaching
β
β
attached
attached
β
detaching
β
β
unbinding
unbind
β
dispose
β
β
container.registerHandler(key, handler)
Registration.callback(key: any, value: ResolveCallback): IRegistration
-
container.registerResolver(key: any, resolver: Resolver)
container.registerResolver(key: any, resolver: IResolver)
-
container.autoRegister(key: any, fn?: Function
β
-
β
Registration.alias(originalKey: any, aliasKey: any): IRegistration
-
@inject(Optional.of(MyService))
@inject(optional(MyService))
-
@inject(Parent.of(MyService))
β
-
@inject(Factory.of(MyService))
@inject(factory(MyService))
-
@inject(NewInstance.of(MyService))
@inject(newInstanceForScope(MyService))
-
β
@inject(newInstanceOf(MyService))
-
appender and sink into the Aurelia container?Finally, The usage
canLoad
canActivate
β
loading
activate
β
canUnload
canDeactivate
β
unloading
deactivate
β
one-time
β
bind
β
$first
β
β
$last
β
β
$even
β
β
$odd
β
β
$length
β
β
${a.b}
syntax:
β
β
observation:
β
β
value conveter
${a | convert: value }
syntax:
β
β
observation:
β
β
binding behavior
${a & behavior: config }
syntax:
β
β
observation:
β
β
function call
${doThing(param)}
syntax:
β
β
observation:
β
β
array methods
${items.join(', ')}
syntax:
β
β
observation (on array):
β
β
lambda
${items.filter(x => x.v > 70)}
syntax:
β
β
observation:
β
β
constructor
constructor
β
define
β
β
hydrating
β
β
binding
Fetch data (working with API services & Ajax calls), initialize data/subscriptions.
bound
Any work that relies on fromView/twoWay binding data coming from children, Defining router hooks.
attached
Use anything (like third-party libraries) that touches the DOM.
unbinding
Cleanup data/subscriptions, maybe persist some data for the next activation.
dispose
One way cleanup all the references/resources. This is invoked only once and is irreversible
container.createChild()
DI.createContainer()
-
container.registerSingleton(key: any, fn?: Function)
Registration.singleton(key: any, value: Function): IRegistration
-
container.registerTransient(key: any, fn?: Function)
Registration.transient(key: any, value: Function): IRegistration
-
container.registerInstance(key: any, instance?: any)
Registration.transient(key: any, value: any): IRegistration
container.get(MyService)
container.get(MyService)
-
β
container.getAll(MyService)
-
@singleton
β
-
@transient
β
-
@inject(MyService)
@inject(MyService)
-
@autoinject()
β
@inject(Lazy.of(MyService))
@inject(lazy(MyService))
-
@inject(All.of(MyService))
@inject(all(MyService))
canActivate
if the component can be activated.
activate
when the component gets activated.
canDeactivate
if the component can be deactivated.
deactivate
when the component gets deactivated.
${ }
β
one-way
β
to-view
β
from-view
β
two-way
β
call
β
trigger
β
delegate
β
capture
β
$this
β
The view-model that your binding expressions are being evaluated against.
$event
β
The DOM Event in delegate, trigger, and capture bindings.
$parent
β
β
$parent.$parent.$parent.name
β
β
$index
β
β
β
β
property
${a}
syntax:
β
β
observation:
β
β
// webpack.config.js
β
// ...
entry: test ? './test/all-spec.ts' : './src/main.ts' /*Here*/,
// ...<!-- index.ejs -->
β
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Aurelia</title>
</head>
<body aurelia-app="main">
<script src="scripts/vendor-bundle.js"
data-main="aurelia-bootstrapper"></script>
</body>
</html><!-- index.ejs -->
β
<!DOCTYPE html>
<html>
β
<head>
<meta charset="utf-8">
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
β
<body>
<my-app></my-app>
</body>
β
</html>-
-
member
// src/main(.js|.ts)
β
import { RouterConfiguration } from '@aurelia/router';
import Aurelia from 'aurelia';
import { MyApp } from './my-app';
β
Aurelia
.register(RouterConfiguration.customize({ useUrlFragmentHash: false }))
.app(MyApp)
.start();// src/main(.js|.ts)
β
export function configure(aurelia: Aurelia): void {
aurelia.use
.standardConfiguration()
.feature(PLATFORM.moduleName('resources/index'))
.globalResources(PLATFORM.moduleName('./bar/nav-bar'));
β
aurelia.use.developmentLogging(environment.debug ? 'debug' : 'warn');
β
if (environment.testing) {
aurelia.use.plugin(PLATFORM.moduleName('aurelia-testing'));
}
β
aurelia.start()
.then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
}<!-- View -->
<!-- src/app.html -->
β
<require from="./styles.css"></require>
<require from="./nav-bar.html"></require>
<template>
<h1>${message}</h1>
</template>// ViewModel
// src/app(.js|.ts)
β
export class App {
constructor() {
this.message = 'Hello World!';
}
}<!-- View -->
<!-- src/my-app.html -->
β
<import from="./welcome"></import>
<import from="./about.html"></import>
<div class="message">${message}</div>// ViewModel
// src/my-app(.js|.ts)
β
export class MyApp {
public message = 'Hello World!';
}/* Style */
/* src/my-app.css */
β
nav {
background: #eee;
display: flex;
}
a {
padding: 10px;
text-decoration: none;
color: black;
}
a:hover {
background-color: darkgray;
}
.load-active {
background-color: lightgray;
}// Aurelia 2
import { inject, lazy, all, optional, newInstanceOf, factory } from "@aurelia/kernel";export class ConsoleAppender {
debug(logger, ...rest) {
console.debug(`DEBUG [${logger.id}]`, ...rest);
}
info(logger, ...rest) {
console.info(`INFO [${logger.id}]`, ...rest);
}
warn(logger, ...rest) {
console.warn(`WARN [${logger.id}]`, ...rest);
}
error(logger, ...rest) {
console.error(`ERROR [${logger.id}]`, ...rest);
}
}import * as LogManager from 'aurelia-logging';
import { ConsoleAppender } from 'aurelia-logging-console';
export function configure(aurelia) {
aurelia.use.standardConfiguration();
LogManager.addAppender(new ConsoleAppender());
LogManager.setLevel(LogManager.logLevel.debug);
aurelia.start().then(() => aurelia.setRoot());
}// main(.js|.ts)
β
import Aurelia, { ConsoleSink, LoggerConfiguration, LogLevel } from 'aurelia';
import { MyApp } from './my-app';
β
Aurelia
// Here
.register(LoggerConfiguration.create({
level: LogLevel.trace,
sinks: [ConsoleSink]
}))
.app(MyApp)
.start();
βimport { ILogger } from "@aurelia/kernel";
β
export class MyApp {
constructor(@ILogger private readonly logger: ILogger /* Here */) {
logger.warn("warning!");
}
}import { IConsoleLike } from '@aurelia/kernel';
β
class ConsoleAppender implements IConsoleLike {
public debug(...args: unknown[]): void {
console.debug(...args);
}
β
public info(...args: unknown[]): void {
console.info(...args);
}
β
public warn(...args: unknown[]): void {
console.warn(...args);
}
β
public error(...args: unknown[]): void {
console.error(...args);
}
}import { LogLevel } from 'aurelia';
import { sink, ISink, ILogEvent, } from '@aurelia/kernel';
β
@sink({ handles: [LogLevel.debug] })
class EventLog implements ISink {
public readonly log: ILogEvent[] = [];
public handleEvent(event: ILogEvent): void {
this.log.push(event);
}
}// Angular binding syntax simulation
// <input [disabled]="condition ? true : false">
@attributePattern({ pattern: '[PART]', symbols: '[]' })
export class AngularOneWayBindingAttributePattern {
public ['[PART]'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'one-way');
}
}
// <input [(ngModel)]="name">
@attributePattern({ pattern: '[(PART)]', symbols: '[()]' })
export class AngularTwoWayBindingAttributePattern {
public ['[(PART)]'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'two-way');
}
}
// <input #phone placeholder="phone number" />
@attributePattern({ pattern: '#PART', symbols: '#' })
export class AngularSharpRefAttributePattern {
public ['#PART'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, parts[0], 'element', 'ref');
}
}<import from="./name-tag">
β
<h2>${message} <name-tag name.bind="to"></name-tag>!</h2>
<button click.trigger="leave()">Leave</button>import { LoggerConfiguration, LogLevel } from 'aurelia';
β
// Instantiation
const consoleLogger = new ConsoleAppender();
β
Aurelia
// Registration
.register(LoggerConfiguration.create({
$console: consoleLogger,
level: LogLevel.trace,
sinks: [EventLog]
}))
.app(MyApp)
.start();import { ILogger } from "@aurelia/kernel";
β
export class MyApp {
constructor(@ILogger logger: ILogger) {
logger.debug("debug!");
}
}// components/registry.ts
β
export * from './say-hello';
export * from './name-tag';// main.ts
β
import Aurelia from 'aurelia';
import { App } from './app';
import * as globalComponents from './components/registry';
β
Aurelia
.register(
globalComponents // This globalizes all the exports of the registry.
)
.app(App)
.start();