Extending the binding engine
Learn how to extend Aurelia's binding language with custom binding commands, attribute patterns, and template syntax extensions.
Aurelia's binding system is powerful and extensible by design. This advanced scenario teaches you how to create custom binding commands (like .bind, .trigger), define attribute patterns, and extend the template compiler to add your own domain-specific syntax to Aurelia templates.
Why This Is an Advanced Scenario
Extending the binding engine requires deep understanding of:
Template compilation - How Aurelia parses and compiles templates
Binding instructions - Low-level directives that drive the rendering pipeline
Attribute patterns - How Aurelia recognizes and categorizes attributes
Expression parsing - AST manipulation and custom expression types
Rendering pipeline - How instructions become live bindings
Framework internals - Runtime architecture and lifecycle hooks
Use cases for binding engine extensions:
Custom DSLs - Domain-specific template languages
Framework integration - Adapt other framework syntaxes
Performance optimization - Specialized binding modes for specific use cases
Developer experience - Shorthand syntax for common patterns
Legacy migration - Support old syntax during gradual upgrades
Complete Guides
Aurelia provides several extension points for the binding system:
1. Custom Binding Commands
Create new binding commands like .my-bind or .special-trigger:
See the complete guide: Extending the Binding Language
2. Attribute Patterns
Define how Aurelia recognizes and interprets attributes:
See the complete guide: Modifying Template Parsing with AttributePattern
3. Template Syntax Extensions
Add entirely new syntax features to templates:
See the complete guide: Extending Templating Syntax
4. Attribute Mapping
Customize how attribute names map to properties:
See the complete guide: Attribute Mapping
5. Template Compiler Extensions
Hook into the compilation process:
See the complete guide: Extending the Template Compiler
Quick Example: Custom Binding Command
Here's a simple custom binding command that logs binding updates:
import { bindingCommand } from '@aurelia/template-compiler';
import { PropertyBindingInstruction } from '@aurelia/runtime-html';
@bindingCommand('debug')
export class DebugBindingCommand {
public get ignoreAttr(): boolean { return false; }
public build(info, parser) {
const expression = parser.parse(info.attr.rawValue, 'IsProperty');
// Wrap the expression with logging
const wrappedExpr = {
...expression,
evaluate: (scope, binding) => {
const value = expression.evaluate(scope, binding);
console.log(`[DEBUG] ${info.attr.target}:`, value);
return value;
}
};
return new PropertyBindingInstruction(
wrappedExpr,
info.attr.target,
'toView'
);
}
}Usage in templates:
<div class.debug="isActive ? 'active' : 'inactive'">
<!-- Logs class value changes to console -->
${message}
</div>Quick Example: Attribute Pattern
Create a shorthand syntax like @click instead of click.trigger:
import { attributePattern } from '@aurelia/template-compiler';
@attributePattern({ pattern: '@PART', symbols: '@' })
export class AtPrefixedTriggerAttributePattern {
public ['@PART'](name: string, value: string, parts: string[]): string {
return `${parts[0]}.trigger`;
}
}Register it:
import Aurelia from 'aurelia';
import { AtPrefixedTriggerAttributePattern } from './at-pattern';
Aurelia
.register(AtPrefixedTriggerAttributePattern)
.app(MyApp)
.start();Usage:
<!-- Instead of click.trigger="handleClick()" -->
<button @click="handleClick()">Click Me</button>
<!-- Instead of submit.trigger="handleSubmit()" -->
<form @submit="handleSubmit()">...</form>What You'll Learn
The complete guides cover:
Binding Commands
Basic structure - The
BindingCommandInstanceinterfaceInstruction building - Creating PropertyBinding, TriggerBinding, etc.
Expression parsing - Working with the expression parser
Registration - Making commands available globally
Real-world examples - Two-way, one-time, delegate, capture commands
Attribute Patterns
Pattern syntax - Defining recognition rules
Dynamic transformation - Converting attributes at compile time
Symbol usage - Dots, colons, at-signs, and custom symbols
Multi-part patterns - Complex attribute structures
Built-in patterns - Understanding Aurelia's defaults
Template Syntax
Custom template controllers - if, repeat, switch-like syntax
Custom elements - Component-level extensions
Custom attributes - Behavior-modifying directives
Value converters - Transformation pipelines
Binding behaviors - Runtime binding modifications
Common Extension Patterns
Shorthand Syntax
// Create :property shorthand for property.bind
@attributePattern({ pattern: ':PART', symbols: ':' })
export class ColonPrefixedBindPattern {
[':PART'](name, value, parts) {
return `${parts[0]}.bind`;
}
}<!-- Use :value instead of value.bind -->
<input :value="username" type="text">Specialized Binding Modes
// Create .once binding that only binds on first render
@bindingCommand('once')
export class OnceBindingCommand {
build(info, parser) {
const expr = parser.parse(info.attr.rawValue, 'IsProperty');
return new PropertyBindingInstruction(expr, info.attr.target, 'oneTime');
}
}Framework Syntax Adapters
// Support Vue-style v-model syntax
@attributePattern({ pattern: 'v-model', symbols: 'v-' })
export class VueModelPattern {
['v-model'](name, value) {
return 'value.two-way';
}
}<!-- Vue-style syntax in Aurelia -->
<input v-model="username">
<!-- Becomes: <input value.two-way="username"> -->Architecture Overview
Template Source
↓
Template Compiler
↓
Attribute Recognition (AttributePattern)
↓
Binding Command Lookup
↓
Instruction Building (BindingCommand)
↓
Compiled Instructions
↓
Runtime Rendering
↓
Live BindingsExtension Points
Binding Command
Custom binding modes
Medium
Low
Attribute Pattern
Syntax shortcuts
Easy
None (compile-time)
Template Controller
Control flow
Medium
Low-Medium
Custom Renderer
Instruction handling
Hard
Medium
Expression Transformer
AST manipulation
Hard
Low
Best Practices
Keep it simple - Complex extensions are hard to maintain
Document thoroughly - Custom syntax needs clear documentation
Test extensively - Edge cases in parsing are common
Consider performance - Binding overhead accumulates
Follow conventions - Match Aurelia's naming and patterns
Provide TypeScript support - Type definitions for IntelliSense
Performance Considerations
Compile-time extensions (AttributePattern) have zero runtime cost
Binding commands have minimal per-binding overhead
Custom expressions should cache computed values
Avoid complex wrapping - Each layer adds overhead
Debugging Extensions
import { ILogger } from '@aurelia/kernel';
import { resolve } from '@aurelia/kernel';
@bindingCommand('my-bind')
export class MyBindingCommand {
private logger = resolve(ILogger);
build(info, parser) {
this.logger.debug('Building binding:', info);
// ... implementation
}
}Migration from Aurelia 1
Key differences:
Decorator-based - Use
@bindingCommandand@attributePatternTypeScript-first - Better type inference
Simplified APIs - Fewer required methods
Better composition - Easier to combine extensions
Ready to Extend?
Start with these guides based on your needs:
Simple shortcuts? → Attribute Patterns
Custom binding modes? → Binding Commands
New syntax features? → Extending Templating Syntax
Deep integration? → Extending the Template Compiler
Additional Resources
Last updated
Was this helpful?