Attribute transferring

Forward bindings from a custom element to its inner template using Aurelia's spread operators.

Attribute transferring lets a custom element pass bindings it receives down to elements inside its own template. It keeps component APIs small while still exposing the flexibility callers need.

As an application grows, the components inside it also grow. Something that starts simple like the following component

export class FormInput {
  @bindable label
  @bindable value
}

with the template

<label>${label}
  <input value.bind="value">
</label>

can quickly grow out of hand with a number of needs for configuration: aria, type, min, max, pattern, tooltip, validation etc...

After a while, the FormInput component above will become more and more like a relayer that simply passes bindings through. The number of @bindable properties increases and maintenance becomes tedious:

export class FormInput {
  @bindable label
  @bindable value
  @bindable type
  @bindable tooltip
  @bindable arias
  @bindable etc
}

And the usage of such element may look like this

to be repeated like this inside:

Coordinating all of those bindings isn't difficult, just repetitive. Attribute transferring, conceptually similar to JavaScript spread syntax, reduces the template to:

This moves the bindings declared on <form-input> onto the <input> element inside the component.

Aurelia Spread Operators Overview

Aurelia provides several spread operators for different use cases:

Operator
Purpose
Example

...$attrs

Spread captured attributes from parent element

<input ...$attrs>

...$bindables

Spread object properties to bindable properties

<my-component ...$bindables="user">

...expression

Shorthand for bindable spreading

<my-component ...user>

Each operator serves a specific purpose in component composition and data flow.

Usage

To transfer attributes and bindings from a custom element, there are two steps:

  • Set capture to true on a custom element via @customElement decorator:

This tells the template compiler to capture bindings and attributes (with some exceptions) for later reuse.

  • Spread the captured attributes onto an element:

How it works

What attributes are captured

Everything except template controller and custom element bindables are captured. For the following example:

View model:

Usage:

What are captured:

  • value.bind="extraComment"

  • class="form-control"

  • style="background: var(--theme-purple)"

  • tooltip="Hello, ${tooltip}" What are not captured:

  • if.bind="needsComment" (if is a template controller)

  • label.bind="label" (label is a bindable property)

How will attributes be applied in ...$attrs

Attributes that are spread onto an element will be compiled as if it was declared on that element.

This means .bind command will work as expected when it's transferred from some element onto some element that uses .two-way for .bind.

It also means that spreading onto a custom element will also work: if a captured attribute is targeting a bindable property of the applied custom element. An example:

if value is a bindable property of my-input, the end result will be a binding that connects the message property of the corresponding app.html view model with <my-input> view model value property. Binding mode is also preserved like normal attributes.

Advanced Spread Patterns

Mixed Binding Patterns

You can combine multiple spread operators and explicit bindings on the same element:

Binding Priority (last wins):

  1. ...$bindables / ...expression (first)

  2. ...$attrs (second)

  3. Explicit bindings (last, highest priority)

Note: According to the existing documentation, ...$attrs will always result in bindings after ...$bindables/$bindables.spread/...expression, regardless of their order in the template.

Complex Member Access

Spread operators support complex expressions:

Conditional Spreading

You can conditionally spread attributes based on expressions:

Automatic Expression Inference

Aurelia can automatically infer property names in certain binding scenarios:

Shorthand Binding Syntax

Inference Rules

  • Property name must match the attribute name exactly

  • Only works with simple property access (no expressions)

  • Works with all binding commands (.bind, .two-way, .one-way, etc.)

  • Case-sensitive: firstName.bind infers firstName, not firstname

Performance Considerations

Binding Creation Optimization

Spread operators include several performance optimizations:

One-time Change Detection

Spread operations are optimized to prevent unnecessary binding updates:

Memory Usage Guidelines

  • Spread operators create bindings for each property accessed

  • Large objects with many properties create many bindings

  • Consider using specific bindable properties for frequently changing data

  • Use spreading primarily for configuration and setup data

Error Handling & Edge Cases

Null and Undefined Handling

Spread operators handle null and undefined values gracefully:

Invalid Expressions

Type Safety with TypeScript

TypeScript provides compile-time validation for spread operations:

Advanced Capture Patterns

Capture Filtering

Filter which attributes are captured using a function:

Multi-level Capture Guidelines

Template Controller Compatibility

Spread operators work with template controllers:

Integration Examples

Component Composition

Usage:

Working with Third-party Components

Dynamic Component Creation

Common Patterns and Best Practices

1. Configuration Objects

2. Conditional Properties

3. Proxy Objects for Transformation

4. Default Values with Spreading

This comprehensive documentation now covers all the advanced patterns and edge cases developers might encounter when working with Aurelia's spread operators.

Last updated

Was this helpful?