Spread Binding (.spread)
The .spread binding command allows you to bind multiple properties from an object to a custom element's bindable properties or to an HTML element's attributes in a single, concise expression. This is particularly useful when you have an object with multiple properties that match the bindable properties of a custom element or attributes of an HTML element.
Overview
Instead of binding each property individually:
<user-card
name.bind="user.name"
email.bind="user.email"
avatar.bind="user.avatarUrl"
role.bind="user.role">
</user-card>You can use .spread to bind all matching properties at once:
<user-card user.spread="user"></user-card>Basic Usage with Custom Elements
The .spread binding is most powerful when used with custom elements that have multiple bindable properties:
// user-card.ts
import { bindable } from 'aurelia';
export class UserCard {
@bindable name: string;
@bindable email: string;
@bindable avatarUrl: string;
@bindable role: string;
}<!-- my-app.html -->
<user-card user.spread="currentUser"></user-card>// my-app.ts
export class MyApp {
currentUser = {
name: 'Jane Doe',
email: '[email protected]',
avatarUrl: 'https://example.com/avatar.jpg',
role: 'Administrator'
};
}Result: The user-card component receives all matching properties from currentUser. Properties like name, email, avatarUrl, and role are automatically bound to their respective bindable properties.
How It Works
The .spread binding:
Evaluates the expression to get an object
Identifies matching properties between the object and the target's bindable properties (for custom elements) or valid attributes (for HTML elements)
Creates individual bindings for each matching property
Updates dynamically when the source object changes
Property Matching
For custom elements, only properties that are declared as @bindable are bound:
export class ProductCard {
@bindable name: string; // Will be bound if exists in spread object
@bindable price: number; // Will be bound if exists in spread object
description: string; // Won't be bound (not @bindable)
}<product-card data.spread="product"></product-card>export class MyApp {
product = {
name: 'Laptop',
price: 999,
description: 'A powerful laptop', // This won't be bound (not @bindable)
category: 'Electronics' // This won't be bound (not @bindable)
};
}For HTML elements, all standard HTML attributes can be bound:
<input attrs.spread="inputConfig">export class MyApp {
inputConfig = {
type: 'email',
placeholder: 'Enter your email',
required: true,
maxlength: 100
};
}Dynamic Object Updates
When the source object changes, the bindings update automatically:
import { resolve } from '@aurelia/kernel';
export class DynamicProfile {
user = {
name: 'John',
email: '[email protected]',
role: 'User'
};
upgradeToAdmin() {
// This will automatically update the spread bindings
this.user = {
name: 'John',
email: '[email protected]',
role: 'Administrator' // Changed
};
}
updateName(newName: string) {
// This will also update the spread bindings
this.user = {
...this.user,
name: newName
};
}
}<user-card profile.spread="user"></user-card>
<button click.trigger="upgradeToAdmin()">Upgrade to Admin</button>
<button click.trigger="updateName('Jane')">Change Name</button>Combining Spread with Individual Bindings
You can mix .spread bindings with individual property bindings. Individual bindings take precedence:
export class MyApp {
userDefaults = {
name: 'Guest',
email: '[email protected]',
role: 'Visitor'
};
adminEmail = '[email protected]';
}<!-- Spread provides defaults, but email is overridden -->
<user-card
defaults.spread="userDefaults"
email.bind="adminEmail">
</user-card>Result: The user-card receives name and role from userDefaults, but email is bound to adminEmail specifically.
Spreading HTML Element Attributes
The .spread binding works with regular HTML elements too:
export class FormBuilder {
textInputConfig = {
type: 'text',
placeholder: 'Enter text',
class: 'form-control',
required: true,
minlength: 3,
maxlength: 50
};
emailInputConfig = {
type: 'email',
placeholder: 'Enter email',
class: 'form-control',
required: true
};
}<form>
<input config.spread="textInputConfig">
<input config.spread="emailInputConfig">
</form>Spreading to Multiple Custom Elements
You can use the same object with different custom elements:
export class Dashboard {
cardData = {
title: 'Sales Report',
subtitle: 'Q4 2024',
value: '$1.2M',
change: '+15%',
icon: 'trending-up'
};
}<info-card data.spread="cardData"></info-card>
<metric-widget data.spread="cardData"></metric-widget>
<summary-panel data.spread="cardData"></summary-panel>Each component receives only the properties it defines as @bindable.
Strict Mode
By default, .spread operates in non-strict mode, which means it silently ignores properties that don't match bindable properties. In development builds, warnings may be logged when spreading non-object values.
export class MyApp {
// This will work but only matching properties are bound
userData = {
name: 'Alice',
email: '[email protected]',
unknownProp: 'ignored' // Silently ignored if not @bindable
};
// This will log a warning in dev mode
invalidData = null;
}<user-card data.spread="userData"></user-card>
<user-card data.spread="invalidData"></user-card> <!-- Warning in dev -->Performance Considerations
Efficient Updates
The .spread binding uses caching to minimize unnecessary work:
Binding cache: Bindings for each property are created once and reused
Scope cache: Binding scopes are cached per object instance
Smart updates: Only properties that exist in both the source object and target are bound
When to Use Spread
Use .spread when:
You have an object with multiple properties that map to bindable properties
You're working with data from APIs or state management
You want to reduce template verbosity
The source object structure matches the target's bindable properties
Avoid .spread when:
You only need to bind one or two properties (use individual bindings)
You need fine-grained control over each binding's mode (
.two-way,.one-time, etc.)The source object has many properties that don't match the target (creates unnecessary overhead)
Complete Example: Dynamic Form
Here's a comprehensive example showing how to build dynamic forms with .spread:
// form-field.ts
import { bindable } from 'aurelia';
export class FormField {
@bindable label: string;
@bindable type: string = 'text';
@bindable value: string;
@bindable placeholder: string;
@bindable required: boolean = false;
@bindable disabled: boolean = false;
@bindable error: string;
}<!-- form-field.html -->
<div class="form-field">
<label if.bind="label">${label}</label>
<input
type.bind="type"
value.bind="value"
placeholder.bind="placeholder"
required.bind="required"
disabled.bind="disabled">
<span class="error" if.bind="error">${error}</span>
</div>// registration-form.ts
export class RegistrationForm {
fields = {
username: {
label: 'Username',
type: 'text',
placeholder: 'Enter username',
required: true,
value: ''
},
email: {
label: 'Email Address',
type: 'email',
placeholder: 'Enter email',
required: true,
value: ''
},
password: {
label: 'Password',
type: 'password',
placeholder: 'Enter password',
required: true,
value: ''
},
bio: {
label: 'Biography',
type: 'textarea',
placeholder: 'Tell us about yourself',
required: false,
value: ''
}
};
submit() {
const formData = Object.keys(this.fields).reduce((acc, key) => {
acc[key] = this.fields[key].value;
return acc;
}, {} as Record<string, string>);
console.log('Form submitted:', formData);
}
}<!-- registration-form.html -->
<form>
<form-field field.spread="fields.username"></form-field>
<form-field field.spread="fields.email"></form-field>
<form-field field.spread="fields.password"></form-field>
<form-field field.spread="fields.bio"></form-field>
<button type="button" click.trigger="submit()">Submit</button>
</form>Spreading with Repeaters
Combine .spread with repeat.for to dynamically generate components from data:
export class DataGrid {
columns = [
{ name: 'id', label: 'ID', sortable: true, width: 60 },
{ name: 'name', label: 'Name', sortable: true, width: 200 },
{ name: 'email', label: 'Email', sortable: false, width: 250 },
{ name: 'role', label: 'Role', sortable: true, width: 120 }
];
}<table>
<thead>
<tr>
<table-header
repeat.for="column of columns"
config.spread="column">
</table-header>
</tr>
</thead>
</table>Nested Spread Bindings
The .spread binding can be nested to handle captured attributes in parent-child custom element scenarios:
<!-- parent-component.html -->
<child-component outer.spread="..."></child-component>The spread binding will automatically handle captured attributes and pass them through the component hierarchy. This is particularly useful when building wrapper components.
Integration with State Management
Spread bindings work naturally with state management solutions:
import { Store } from '@aurelia/store-v1';
import { connectTo } from '@aurelia/store-v1';
@connectTo()
export class UserProfile {
state: State;
// User object from store
get user() {
return this.state.currentUser;
}
}<!-- Spread the user from the store -->
<user-details data.spread="user"></user-details>
<user-preferences settings.spread="user.preferences"></user-preferences>Debugging Spread Bindings
In development mode, you can inspect which properties were bound:
export class DebugComponent {
@bindable name: string;
@bindable email: string;
@bindable role: string;
binding() {
// Check what was bound
console.log('Name:', this.name);
console.log('Email:', this.email);
console.log('Role:', this.role);
}
}If properties aren't binding as expected:
Verify @bindable decorators: Ensure target properties are decorated with
@bindableCheck property names: Property names must match exactly (case-sensitive)
Inspect the source object: Log the object being spread to verify it contains expected properties
Watch for warnings: Development mode logs warnings for non-object values
Comparison with Other Approaches
Traditional Individual Bindings
<!-- Verbose but explicit -->
<user-card
name.bind="user.name"
email.bind="user.email"
role.bind="user.role">
</user-card>Pros: Clear, explicit, supports different binding modes per property Cons: Verbose, repetitive, harder to maintain
Spread Binding
<!-- Concise and maintainable -->
<user-card data.spread="user"></user-card>Pros: Concise, reduces repetition, easy to maintain Cons: Less explicit, all properties use .to-view binding mode
Passing Whole Object as Bindable
<!-- Pass entire object -->
<user-card user.bind="user"></user-card>Pros: Very concise, passes all data Cons: Requires component to accept object, tighter coupling
Best Practices
Use descriptive names: Name the spread binding to indicate what it spreads (e.g.,
user.spread,config.spread,props.spread)Document expected properties: In your custom element, document which properties are expected from a spread binding
/**
* User card component
*
* @spread user - Expects: name, email, avatarUrl, role
*/
export class UserCard {
@bindable name: string;
@bindable email: string;
@bindable avatarUrl: string;
@bindable role: string;
}Provide defaults: Use default values for optional properties
export class ConfigurableCard {
@bindable title: string = 'Untitled';
@bindable size: string = 'medium';
@bindable color: string = 'blue';
}Validate in lifecycle hooks: Validate required properties in
binding()orbound()hooks
export class UserCard {
@bindable name: string;
@bindable email: string;
binding() {
if (!this.name || !this.email) {
throw new Error('UserCard requires name and email properties');
}
}
}See Also
Bindable Properties - Defining bindable properties on custom elements
Custom Elements - Building custom elements
Attribute Binding - Standard attribute binding syntax
Property Binding - Binding to component properties
Last updated
Was this helpful?