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

Was this helpful?

Export as PDF
  1. Developer Guides
  2. Migrating to Aurelia 2

Side-by-side comparison

Bootstrapping

The hosting page

The first entry point of an Aurelia application is the main HTML page-loading and hosting.

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

aurelia-app attribute helps us to introduce our entry point, themain.tsfile, which includes the configurations of the project.

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

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.

// webpack.config.js
​
// ...
entry: test ? './test/all-spec.ts' :  './src/main.ts' /*Here*/,
// ...

The main module

All the initial settings for starting and working with an Aurelia project are done in this file.

// 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')));
}

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.

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.

// 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();

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:

// 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();

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

Components

The Root Component

The root of any Aurelia application is a single component, which contains everything within the application, actually, the root component.

<!-- 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!';
    }
}
  • To import any style, component or etc, you should userequire.

  • Wrapping the whole HTML content viatemplateisnecessary.

<!-- 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;
}
  • 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.

<import from="./name-tag">
​
<h2>${message} <name-tag name.bind="to"></name-tag>!</h2>
<button click.trigger="leave()">Leave</button>
  • Wrapping the whole HTML content via template is optional.

The Component Life-cycle

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

Name
Aurelia 1
Asyncable
Description

constructor

constructor

✗

define

✗

✗

hydrating

✗

✗

hydrated

✗

✗

created

created

✗

binding

bind

✓

bound

✗

✓

attaching

✗

✓

attached

attached

✓

detaching

✗

✓

unbinding

unbind

✓

dispose

✗

✗

Which life-cycle hooks are most used?

Such cases can be summarized.

Name
When using it

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

Dependency injection

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.

Registering services

Aurelia 1
Aurelia 2
Description

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

-

Resolving services

Aurelia 1
Aurelia 2
Description

container.get(MyService)

container.get(MyService)

-

✗

container.getAll(MyService)

-

Registration strategies

Name
Aurelia 1 & 2
Description

@singleton

✓

-

@transient

✓

-

Resolvers

// Aurelia 2
import { inject, lazy, all, optional, newInstanceOf, factory } from "@aurelia/kernel";
Aurelia 1
Aurelia 2
Description

@inject(MyService)

@inject(MyService)

-

@autoinject()

✗

@inject(Lazy.of(MyService))

@inject(lazy(MyService))

-

@inject(All.of(MyService))

@inject(all(MyService))

-

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

-

Logging

Writing debug output while developing is great. This is how you can do this with Aurelia.

Write an appender.

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);
    }
}

In the main(.js|.ts)

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());
}

You can register LoggerConfiguration as following

// 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();
​

Usage

import { ILogger } from "@aurelia/kernel";
​
export class MyApp {
    constructor(@ILogger private readonly logger: ILogger /* Here */) {
        logger.warn("warning!");
    }
}

How to write an appender?

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);
  }
}

How to write a sink?

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);
  }
}

How to register appender and sink into the Aurelia container?

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();

Finally, The usage

import { ILogger } from "@aurelia/kernel";
​
export class MyApp {
    constructor(@ILogger logger: ILogger) {
        logger.debug("debug!");
    }
}

Router

Routing Life-cycle

Name
Description

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.

Name
Aurelia 1
Asyncable
Description

canLoad

canActivate

✓

loading

activate

✓

canUnload

canDeactivate

✓

unloading

deactivate

✓

Binding

String Interpolation

Name
Aurelia 1 & 2
Description

${ }

✓

Binding HTML and SVG Attributes

Name
Aurelia 1 & 2
Description

one-way

✓

to-view

✓

from-view

✓

two-way

✓

one-time

✓

bind

✓

References

| Name | Aurelia 1 | Aurelia 2 | Description | | ---- | - | - | | ref | ✓ | ✓ | | | view-model.ref | ✓ | ✓ | deprecated in v2 | | component.ref | ✗ | ✓ | Not in v1 |

Passing Function References

Name
Aurelia 1 & 2
Description

call

✓

DOM Events

Name
Aurelia 1 & 2
Description

trigger

✓

delegate

✓

capture

✓

{% 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 }

Contextual Properties

General

Name
Aurelia 1 & 2
Description

$this

✓

The view-model that your binding expressions are being evaluated against.

Event

Name
Aurelia 1 & 2
Description

$event

✓

The DOM Event in delegate, trigger, and capture bindings.

Repeater

Name
Aurelia 1
Aurelia 2
Description

$parent

✓

✓

$parent.$parent.$parent.name

✓

✓

$index

✓

✓

$first

✓

✓

$last

✓

✓

$even

✓

✓

$odd

✓

✓

$length

✗

✓

@computedFrom

@computedFrom tells the binding system which expressions to observe. When those expressions change, the binding system will re-evaluate the property (execute the getter).

Aurelia 1
Aurelia 2

✓

✗

In Aurelia 2, The framework automatically computes observation without the need for any configuration or decorator.

Observation in template

type
example
type
Aurelia 1
Aurelia 2

property

${a}

syntax:

✓

✓

observation:

✓

✓

member

${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:

✗

✓

@attributePattern

This feature is totally new for Aurelia 2.

// 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');
    }
}
PreviousFor plugin authorsNextCheat Sheet

Last updated 1 year ago

Was this helpful?

Aurelia 1 has a restriction and the community made an plugin that is called after all child components are attached, and after all two-way bindings have completed. Theattachedlife-cycle in version 2 covers this scenario.

afterAttached