All pages
Powered by GitBook
1 of 3

Loading...

Loading...

Loading...

For plugin authors

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.

Cardigan UI
Aurelia 2 Table
Aurelia 2 Auth
Aurelia 2 Google Maps

Migrating to Aurelia 2

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.

COMPAT PACKAGE

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:

  1. installing the compat package via

  1. include the compat package into your app:

BREAKING CHANGES

Event

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

Scope selection

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

Internal binding property observeProperty has been renamed to observe

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

Internal binding property sourceExpression has been renamed to ast

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

Enhance API changes:

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.

View model ref binding (view-model.ref="...")

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.

If attribute (if.bind="...")

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

Binding Engine

  • 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

Binding commands

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

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

General changes

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

Plugins:

Web-Components plugin

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

context. Refer to

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.

  • Better intellisense support for TypeScript applications. Using the new injection interfaces, you can now inject strongly typed Aurelia packages such as Fetch Client, Router or Internationalization. These packages are prefixed with an "I" such as
    IHttpClient
    ,
    IRouter
    and so on.
    event modifier doc here
    dynamic composition doc
    dynamic ui composition doc
    lambda expression
    v1:
    <compose view.bind="...">
    <compose view-model.bind="...">
    
    v2:
    <au-compose template.bind="...">
    <au-compose component.bind="...">
    npm install @aurelia/compat-v1
    import { 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 } }

    Side-by-side comparison

    Bootstrapping

    The hosting page

    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.

    The main module

    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.

    Components

    The Root Component

    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.

    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

    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.

    Which life-cycle hooks are most used?

    Such cases can be summarized.

    Name
    When using it

    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

    Resolving services

    Aurelia 1
    Aurelia 2
    Description

    Registration strategies

    Name
    Aurelia 1 & 2
    Description

    Resolvers

    Aurelia 1
    Aurelia 2
    Description

    Logging

    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?

    Router

    Routing Life-cycle

    Name
    Description

    Binding

    String Interpolation

    Name
    Aurelia 1 & 2
    Description

    Binding HTML and SVG Attributes

    Name
    Aurelia 1 & 2
    Description

    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

    DOM Events

    Name
    Aurelia 1 & 2
    Description

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

    Event

    Name
    Aurelia 1 & 2
    Description

    Repeater

    Name
    Aurelia 1
    Aurelia 2
    Description

    @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

    @attributePattern

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

    -

    How to register appender and sink into the Aurelia container?

    Finally, The usage

    Name
    Aurelia 1
    Asyncable
    Description

    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:

    βœ“

    βœ“

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