Skip to content

Aurelia Binding Commands — One-Way, Two-Way, and Beyond

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Aurelia Binding Commands. We cover key concepts, practical examples, and best practices to help you master this topic.

Aurelia binding commands control how data flows between the ViewModel and View. One-way binding flows data from ViewModel to View. Two-way binding syncs both directions. One-time binding renders once and stops watching. Event commands like delegate and trigger handle user interactions.

What You'll Learn

You will learn every binding command, when to use each, performance implications, and how to choose the right command for your use case.

Why It Matters

Choosing the right binding command affects performance and correctness. One-way binding for static data reduces watchers. Two-way binding for forms keeps data in sync. One-time binding for configuration improves initial render speed.

Real-World Use

A dashboard with 500+ data points uses one-time binding for static labels, one-way binding for live data, two-way for form inputs, and event delegation for button clicks. This binding Strategy keeps the UI responsive with thousands of bound properties.

flowchart LR
    A[Binding Commands] --> B[.bind]
    A --> C[.one-way]
    A --> D[.two-way]
    A --> E[.one-time]
    A --> F[.delegate]
    A --> G[.trigger]
    A --> H[.call]
    B --> I[Default mode]
    C --> J[ViewModel → View]
    D --> K[ViewModel ↔ View]
    E --> L[Render once]

The .bind Command

.bind uses the default binding mode for the property.

<template>
  <!-- String values default to one-way -->
  <h1>${pageTitle}</h1>
  <p textcontent.bind="description"></p>

  <!-- Form input values default to two-way -->
  <input value.bind="searchQuery" />
  <select value.bind="selectedCategory"></select>
</template>

The .one-way Command

.one-way flows data from ViewModel to View only. Changes in the View do not propagate back.

<template>
  <!-- Explicit one-way binding -->
  <span textcontent.one-way="user.name"></span>
  <img src.one-way="user.avatarUrl" />
  <a href.one-way="profileUrl">Profile</a>

  <!-- Best for display-only data -->
  <div class.one-way="priorityClass">
    ${priority | statusLabel}
  </div>
</template>

The .two-way Command

.two-way syncs data in both directions. Changes in the View update the ViewModel and vice versa.

<template>
  <!-- Form inputs -->
  <input value.two-way="user.name" />
  <input type="checkbox" checked.two-way="user.isActive" />
  <select value.two-way="user.role">
    <option value="admin">Admin</option>
    <option value="user">User</option>
  </select>
  <textarea value.two-way="user.bio"></textarea>

  <!-- Custom elements with two-way binding -->
  <date-picker value.two-way="selectedDate"></date-picker>
</template>

The .one-time Command

.one-time renders the value once and never updates. No watcher is created, saving memory and CPU.

<template>
  <!-- Static configuration values -->
  <h1 one-time>${config.appName}</h1>
  <p one-time>Version ${config.version}</p>
  <footer one-time>
    &copy; ${config.currentYear} ${config.company}
  </footer>

  <!-- Labels that never change -->
  <label for="email" one-time>Email Address</label>

  <!-- One-time with textcontent -->
  <span textcontent.one-time="staticLabel"></span>
</template>

The .delegate Command (Event Delegation)

.delegate uses event delegation for better performance with many elements.

<template>
  <!-- Click delegation (efficient for lists) -->
  <ul>
    <li repeat.for="item of items"
        click.delegate="selectItem(item)">
      ${item.name}
    </li>
  </ul>

  <!-- Form events with delegation -->
  <form submit.delegate="onSubmit($event)">
    <input change.delegate="onChange($event)" />
    <input blur.delegate="onBlur($event)" />
    <input focus.delegate="onFocus($event)" />
  </form>

  <!-- Mouse events -->
  <div mouseenter.delegate="onHover()"
       mouseleave.delegate="onLeave()">
    Hover area
  </div>
</template>

The .trigger Command (Direct Events)

.trigger binds events directly to the element. Use when event delegation cannot work (e.g., form events that do not bubble).

<template>
  <!-- Direct event binding -->
  <button click.trigger="save()">Save</button>

  <!-- Non-bubbling events -->
  <input focus.trigger="onFocus()"
         blur.trigger="onBlur()" />

  <!-- Custom events -->
  <my-element custom-event.trigger="handleCustom($event)">
  </my-element>
</template>

The .call Command (Function References)

.call passes a function reference to a child component. The child can call it with arguments.

<template>
  <!-- Parent passes a callback -->
  <child-component on-select.call="handleSelect($event)">
  </child-component>

  <!-- With arguments -->
  <data-table on-row-click.call="showDetail(row)">
  </data-table>
</template>
export class ParentComponent {
  handleSelect(event) {
    console.log('Selected:', event.detail);
  }

  showDetail(row) {
    console.log('Detail:', row);
  }
}

Binding to Attribute Properties

<template>
  <!-- Boolean attributes -->
  <button disabled.bind="isDisabled">Save</button>
  <input readonly.bind="isReadonly" />

  <!-- Style binding -->
  <div style.bind="styleString">Styled div</div>
  <div css="color: ${color}; font-size: ${size}px"></div>

  <!-- Class binding -->
  <div class="btn ${isActive ? 'active' : ''}">Button</div>
  <div class.bind="dynamicClasses"></div>

  <!-- Inner HTML (dangerous — avoid with untrusted content) -->
  <div innerhtml.bind="safeHtml"></div>
</template>

Context Variables in Binding

<template>
  <!-- Repeat context -->
  <div repeat.for="item of items">
    <span>${$index}</span>
    <span>${$first ? 'First' : ''}</span>
    <span>${$last ? 'Last' : ''}</span>
    <span>${$even ? 'Even' : 'Odd'}</span>
    <span>${$odd ? 'Odd' : 'Even'}</span>
    <span>${$parent.someProperty}</span>
  </div>

  <!-- Delegate event context -->
  <button click.delegate="handleClick($event, item)">Click</button>
</template>

Binding to ViewModel Methods

<template>
  <!-- Direct method calls -->
  <button click.delegate="getFullName()">Show Name</button>

  <!-- Method binding in interpolation -->
  <p>${formatDate(item.createdAt)}</p>

  <!-- Method binding in attribute -->
  <input value.bind="formatName(user)" />
</template>

Custom Binding Behavior

<template>
  <!-- Debounce input -->
  <input value.bind="query & debounce:300" />

  <!-- Update on specific events -->
  <input value.bind="name & updateTrigger:'blur'" />

  <!-- One-time with signal for manual update -->
  <div>${message & signal:'refresh'}</div>
</template>

Common Mistakes

  1. Using two-way binding for everything. Two-way binding has more overhead than one-way. Use one-way for display-only data and two-way only for inputs.
  2. Forgetting that .bind defaults differ by element. Inputs default to two-way. Divs default to one-way. This catches developers who expect consistent behavior.
  3. Using .trigger when .delegate is more efficient. Delegate uses a single listener at the document level. Trigger adds a listener per element.
  4. Not passing $event when needed. Event handler methods need $event to access event properties. Pass it explicitly: click.delegate="handle($event)".
  5. Binding complex expressions in templates. Template expressions should be simple. Complex logic belongs in the ViewModel.

Practice Questions

  1. What is the difference between .one-way and .two-way?
  2. When should you use .one-time binding?
  3. What is the performance benefit of .delegate over .trigger?
  4. How does .call differ from .bind for function passing?
  5. Challenge: Create a form with 10+ fields that uses appropriate binding commands. Use one-time for labels, two-way for inputs, delegate for buttons, one-way for validation messages, and call for custom component communication. Profile the binding count with Aurelia's debug tools.

FAQ

What is the default binding command?

It depends on the property. Form values default to two-way. Most other properties default to one-way.

Can I change the default binding mode?

Yes, use @bindable({ defaultBindingMode: bindingMode.oneTime }) in custom elements.

Does Aurelia support string interpolation?

Yes. ${expression} in templates creates a one-way binding by default.

{{< faq "How do I bind to a child element's property?" "Use `` for parent-to-child binding." >}}
What is the `$event` variable?

It is the DOM event object available in event handler expressions.

Mini Project

Create a performance-optimized data table that renders 1000 rows. Use .one-time for static column headers, .delegate for row click events, .one-way for cell data, and .two-way for editable cells. Measure the binding count and render time. Compare performance with an unoptimized version that uses .bind for everything.

What's Next

Now that you understand binding commands, learn Aurelia Conditional Rendering for dynamic templates. Then explore Aurelia List Rendering for collection displays.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro