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:
...$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
capturetotrueon a custom element via@customElementdecorator:
This tells the template compiler to capture bindings and attributes (with some exceptions) for later reuse.
Spread the captured attributes onto an element:
Avoid chaining attribute transfer across many component layers. Deep “prop drilling” is difficult to follow and can become a maintenance burden. Keep transfers to at most one or two levels.
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"(ifis a template controller)label.bind="label"(labelis 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):
...$bindables/...expression(first)...$attrs(second)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.bindinfersfirstName, notfirstname
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
Best Practice: Limit capture levels to 2-3 maximum to maintain code clarity and avoid prop-drilling anti-patterns.
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?