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
  • How it works
  • Extending the attribute mapping
  • Use two-way binding for attribute
  • Live example

Was this helpful?

Export as PDF
  1. Components
  2. Template compilation

Attribute mapping

Learn about binding values to attributes of DOM elements and how to extend the attribute mapping with great ease.

PreviousUsing the template compilerNextRouting

Last updated 1 year ago

Was this helpful?

When dealing with Aurelia and custom elements, we tend to use the @bindable decorator to define bindable properties. The bindable properties are members of the underlying view model class. However, there are cases where we want to work directly with attributes of the DOM elements.

For example, we want an <input> element with a maxlength attribute and map a view model property to the attribute. Let us assume that we have the following view model class:

export class App {
  private inputMaxLength: number = 10;
  private input: HTMLInputElement;
}

Then, intuitively, we would write the following template:

<input maxlength.bind="inputMaxLength" ref="input">

This binds the value to the maxlength attribute of the <input> element. Consequently, the input.maxLength is also bound to be 10. Note that binding the value of the maxLength attribute also sets the value of the maxLength property of the input element. This happens because Aurelia, in the background, does the mapping for us.

On a broad level, this is what attribute mapping is about. This article provides further information about how it works and how to extend it.

How it works

To facilitate the attribute mapping, Aurelia uses IAttrMapper, which has information about how to map an attribute to a property. While creating property binding instructions from , it is first checked if the attribute is a bindable. If it is a bindable property, the attribute name (in kebab-case) is converted to the camelCase property name. However, the attribute mapper is queried for the target property name when it is not a bindable. If the attribute mapper returns a property name, then the property binding instruction is created with that property name. Otherwise, the standard camelCase conversion is applied.

If we want to bind a non-standard <input> attribute, such as fizz-buzz, we can expect the input.fizzBuzz property to be bound. This looks as follows.

<input fizz-buzz.bind="someValue" ref="input">
export class App {
  private someValue: number = 10;
  private input: HTMLInputElement;

  public attached(): void {
    console.log(this.input.fizzBuzz); // 10
  }
}

Extending the attribute mapping

The attribute mapping can be extended by registering new mappings with the IAttrMapper. The IAttrMapper provides two methods for this purpose. The .useGlobalMapping method registers mappings applicable for all elements, whereas the .useMapping method registers mapping for individual elements.

To this end, we can grab the IAttrMapper instance while bootstrapping the app and register the mappings (there is no restriction, however, on when or where those mappings are registered). An example might look as follows.

import {
  AppTask,
  Aurelia,
  IAttrMapper,
} from '@aurelia/runtime-html';

const au = new Aurelia();
au.register(
  AppTask.creating(IAttrMapper, (attrMapper) => {
    attrMapper.useMapping({
      'MY-CE': {
        'fizz-buzz': 'FizzBuzz',
      },
      INPUT: {
        'fizz-buzz': 'fizzbuzz',
      },
    });
    attrMapper.useGlobalMapping({
      'foo-bar': 'FooBar',
    });
  })
);

With this custom mapping registered, we can expect the following to work.

<input fizz-buzz.bind="42" foo-bar.bind="43" ref="input">
<my-ce fizz-buzz.bind="44" foo-bar.bind="45" ref="myCe"></my-ce>
export class App {
  private input: HTMLInputElement;
  private myCe: HTMLElement;

  public attached(): void {
    console.log(this.input.fizzbuzz); // 42
    console.log(this.input.FooBar); // 43
    console.log(this.myCe.FizzBuzz); // 44
    console.log(this.myCe.FooBar); // 45
  }
}

Use two-way binding for attribute

In addition to registering custom mappings, we can teach the attribute mapper when using two-way binding for an attribute. To this end, we can use the .useTwoWay method of the IAttrMapper. The .useTwoWay method accepts a predicate function determining whether the attribute should be bound in two-way mode. The predicate function receives the attribute name and the element name as parameters. If the predicate function returns true, then the attribute is bound in two-way mode, otherwise it is bound in to-view mode.

An example looks as follows.


import {
  AppTask,
  Aurelia,
  IAttrMapper,
} from '@aurelia/runtime-html';

const au = new Aurelia();
au.register(
  AppTask.creating(IAttrMapper, (attrMapper) => {
    // code omitted for brevity
    attrMapper.useTwoWay(
      (el, attr) => el.tagName === 'MY-CE' && attr == 'fizz-buzz'
    );
  })
);

In this example, we are instructing the attribute mapper to use two-way binding for fizz-buzz attribute of <my-ce> element. This means that the following will work.

<my-ce
    ref="myCe"
    foo-bar.bind="myCeFooBar"
    fizz-buzz.bind="myCeFizzBuzz"></my-ce>

myCeFizzBuzz: ${myCeFizzBuzz} myCeFooBar: ${myCeFooBar}
export class MyApp {
  private myCeFooBar: any = 'fizz';
  private myCeFizzBuzz: any = '2424';
  private myCe: HTMLElement & { FooBar?: string; FizzBuzz?: string };
  public attached() {
    setInterval(() => {
      // This change will trigger a change for the myCeFizzBuzz property
      this.myCe.FizzBuzz = Math.ceil(Math.random() * 10_000).toString();

      // This change won't trigger a change for the myCeFooBar property
      this.myCe.FooBar = Math.ceil(Math.random() * 10_000).toString();
    }, 1000);
  }
}

Live example

A similar example can be seen in action below.

In the example above, we are registering a global mapping for foo-bar attribute to FooBar property, which will apply to all elements. We are also registering mappings for individual elements. Note that the key of the object is the of the element; thus, for an element, it needs to be the element name in upper case. In the example above, we map the fizz-buzz attribute differently for <input> and <my-ce> elements.

binding commands
nodeName