Visual Diagrams
Visual diagrams to help understand Aurelia's templating system.
Data Binding Flow
One-Way Binding (View Model → View)
┌─────────────────┐
│ View Model │
│ │
│ message = 'Hi' │
└────────┬────────┘
│
│ .one-way / .to-view
│ or default for most attributes
↓
┌─────────────────┐
│ View │
│ │
│ <p>${message} │
│ ↓ │
│ <p>Hi</p> │
└─────────────────┘Two-Way Binding (View Model ↔ View)
┌─────────────────┐
│ View Model │
│ │
│ name = 'Bob' │
└────────┬────────┘
│
│ .two-way / .bind (default for inputs)
↕
┌─────────────────┐
│ View │
│ │
│ <input │
│ value.bind= │
│ "name"> │
└─────────────────┘
User types "Alice"
↓
View Model updates: name = 'Alice'
↓
All bindings to 'name' update automaticallyFrom-View Binding (View → View Model)
┌─────────────────┐
│ View Model │
│ │
│ query = '' │
└────────▲────────┘
│
│ .from-view
│ (capture only)
│
┌────────┴────────┐
│ View │
│ │
│ <input │
│ value.from- │
│ view="query"> │
└─────────────────┘Binding Mode Decision Tree
Need to bind a value?
│
├─── Is it a form input? (input, textarea, select)
│ │
│ ├─── Need to read user input?
│ │ └─── YES → use .bind (auto two-way)
│ │
│ └─── Only displaying a value? (e.g., readonly)
│ └─── YES → use .one-way
│
└─── Is it a regular attribute? (src, href, class, etc.)
│
├─── Value changes often?
│ └─── YES → use .bind or .one-way
│
└─── Value never changes?
└─── YES → use .one-timeConditional Rendering: if vs show
if.bind - Adds/Removes from DOM
Before (isVisible = false):
┌──────────────────┐
│ DOM │
│ │
│ <div> │
│ <p>Other</p> │
│ </div> │
│ │
│ [content not │
│ in DOM at all] │
└──────────────────┘
After (isVisible = true):
┌──────────────────┐
│ DOM │
│ │
│ <div> │
│ <p>Other</p> │
│ <div if.bind> │ ← Added to DOM
│ Content │
│ </div> │
│ </div> │
└──────────────────┘
Memory: ✓ Freed when hidden
Events: ✓ Cleaned up when hidden
Performance: Best for infrequent changesshow.bind - CSS Display Toggle
Before (isVisible = false):
┌──────────────────┐
│ DOM │
│ │
│ <div> │
│ <p>Other</p> │
│ <div │
│ style= │
│ "display: │
│ none"> │ ← Still in DOM, just hidden
│ Content │
│ </div> │
│ </div> │
└──────────────────┘
After (isVisible = true):
┌──────────────────┐
│ DOM │
│ │
│ <div> │
│ <p>Other</p> │
│ <div> │ ← Display restored
│ Content │
│ </div> │
│ </div> │
└──────────────────┘
Memory: ✗ Always in memory
Events: ✗ Still bound
Performance: Best for frequent togglesDecision Matrix
┌──────────────────┬─────────────┬─────────────┐
│ │ if.bind │ show.bind │
├──────────────────┼─────────────┼─────────────┤
│ DOM Manipulation │ Add/Remove │ CSS Toggle │
│ Memory Usage │ Efficient │ Wasteful │
│ Toggle Speed │ Slower │ Instant │
│ Event Cleanup │ Automatic │ None │
│ Component Init │ Each time │ Once │
│ Use When │ Infrequent │ Frequent │
└──────────────────┴─────────────┴─────────────┘List Rendering with repeat.for
Basic Flow
View Model:
┌─────────────────────────┐
│ items = [ │
│ { id: 1, name: 'A' }, │
│ { id: 2, name: 'B' }, │
│ { id: 3, name: 'C' } │
│ ] │
└───────────┬─────────────┘
│
│ repeat.for="item of items"
↓
View:
┌─────────────────────────┐
│ <li>${item.name}</li> │ → A
│ <li>${item.name}</li> │ → B
│ <li>${item.name}</li> │ → C
└─────────────────────────┘With Keys for Efficient Updates
Without key (by reference):
Items: [A, B, C] → [A, X, B, C]
Aurelia: "Different array order, re-render everything"
Result: All DOM nodes recreated
┌─────────────────────────────────────┐
│ 🔴 Full re-render (expensive) │
└─────────────────────────────────────┘
With key: key.bind="id"
Items: [A, B, C] → [A, X, B, C]
Aurelia: "Same IDs, just moved. Insert X, move B and C"
Result: Reuses existing DOM nodes
┌─────────────────────────────────────┐
│ ✓ Reuses A node │
│ ✓ Creates X node (only new one) │
│ ✓ Reuses B node (moved) │
│ ✓ Reuses C node (moved) │
│ ✅ Minimal DOM operations │
└─────────────────────────────────────┘Contextual Properties
items = ['Apple', 'Banana', 'Cherry']
<div repeat.for="item of items">
$index → 0, 1, 2
$first → true, false, false
$last → false, false, true
$even → true, false, true
$odd → false, true, false
$length → 3, 3, 3
item → 'Apple', 'Banana', 'Cherry'
</div>Event Binding: Trigger vs Capture
Bubbling Phase (.trigger)
Event fires on child element:
Window
↑
Document
↑
<body>
↑
<div> ← .trigger listens here
↑
<button> ← Click starts here
Click!
Event bubbles UP from target to root
Most common use caseCapturing Phase (.capture)
Event fires on child element:
Window
↓
Document
↓
<body>
↓
<div> ← .capture listens here
↓
<button> ← Click will arrive here
Click!
Event captures DOWN from root to target
Rare use case (intercept before children)Event Flow Complete Picture
┌─────────────────────────────────────┐
│ 1. CAPTURE PHASE │
│ (root → target) │
│ │
│ Window → Document → Body → Div │
│ .capture handlers │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. TARGET PHASE │
│ │
│ <button> │
│ Event fires here │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. BUBBLE PHASE │
│ (target → root) │
│ │
│ Button → Div → Body → Document │
│ .trigger handlers │
└─────────────────────────────────────┘Value Converters Pipeline
View Model Value
│
↓
${ price | currency:'USD' | truncate:10 }
│ │ │
│ │ │
↓ ↓ ↓
299.99 → "$299.99 USD" → "$299.99..."
│ │ │
└──────────────┴──────────────┘
│
↓
Rendered in ViewConverter Flow Detail
┌──────────────────┐
│ View Model │
│ price = 299.99 │
└────────┬─────────┘
│
↓
┌──────────────────┐
│ CurrencyConverter│
│ toView(299.99, │
│ 'USD') │
│ → "$299.99 USD" │
└────────┬─────────┘
│
↓
┌──────────────────┐
│ TruncateConverter│
│ toView( │
│ "$299.99 USD", │
│ 10) │
│ → "$299.99..." │
└────────┬─────────┘
│
↓
Final OutputComponent Communication
Parent → Child (Bindable Properties)
┌─────────────────────────────┐
│ Parent Component │
│ │
│ user = { │
│ name: 'Alice', │
│ email: '[email protected]' │
│ } │
│ │
│ <user-card │
│ user.bind="user"> │ ───┐
│ </user-card> │ │
└─────────────────────────────┘ │
│ Passes data down
↓
┌──────────────────────────┐
│ Child Component │
│ (UserCard) │
│ │
│ @bindable user: User; │
│ │
│ <h3>${user.name}</h3> │
│ <p>${user.email}</p> │
└──────────────────────────┘Child → Parent (Call Binding)
┌─────────────────────────────┐
│ Parent Component │
│ │
│ handleDelete(user) { │
│ // Delete user │
│ } │
│ │
│ <user-card │
│ user.bind="user" │
│ on-delete.call= │
│ "handleDelete($event)" │ ◄──┐
│ </user-card> │ │
└─────────────────────────────┘ │
│ Emits event up
│
┌──────────────────────────┐
│ Child Component │
│ (UserCard) │
│ │
│ @bindable onDelete; │
│ │
│ deleteUser() { │
│ this.onDelete?.( │
│ this.user │
│ ); │
│ } │
│ │
│ <button │
│ click.trigger= │
│ "deleteUser()"> │ ───┘
│ Delete │
│ </button> │
└──────────────────────────┘Form Checkbox Collections
How checked.bind with model.bind Works
View Model:
┌────────────────────────────────┐
│ products = [ │
│ { id: 1, name: 'Mouse' }, │
│ { id: 2, name: 'Keyboard' } │
│ ] │
│ │
│ selectedIds = [1] │ ← Array to track selected
└────────────────────────────────┘
View:
┌────────────────────────────────┐
│ <label repeat.for="p of │
│ products"> │
│ <input type="checkbox" │
│ model.bind="p.id" │ ← Value to add/remove
│ checked.bind= │
│ "selectedIds" /> │ ← Array to update
│ ${p.name} │
│ </label> │
└────────────────────────────────┘
User checks "Keyboard":
Aurelia adds 2 to selectedIds → [1, 2]
User unchecks "Mouse":
Aurelia removes 1 from selectedIds → [2]
┌────────────────────────────────┐
│ How it works internally: │
│ │
│ Checkbox checked? │
│ YES → Add model value to │
│ checked array │
│ NO → Remove model value │
│ from checked array │
└────────────────────────────────┘Template Lifecycle
┌─────────────────────────────────────────────────┐
│ Component Created │
└──────────────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ Template Compiled │
│ (Aurelia parses HTML, creates instructions) │
└──────────────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ binding() lifecycle │
│ (Before bindings are applied) │
└──────────────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ Bindings Created & Connected │
│ - value.bind connects properties │
│ - repeat.for sets up collection observer │
│ - Event listeners attached │
└──────────────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ bound() lifecycle │
│ (Bindings are now active) │
└──────────────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ attached() lifecycle │
│ (Component added to DOM) │
└──────────────────────┬──────────────────────────┘
│
↓
┌──────────────────────────┐
│ Component is Active │
│ │
│ Changes to properties │
│ trigger binding updates│
└──────────────┬───────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ detaching() lifecycle │
│ (About to remove from DOM) │
└──────────────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ unbinding() lifecycle │
│ (Cleaning up bindings) │
└──────────────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ Component Removed from DOM │
│ Memory freed, events cleaned up │
└─────────────────────────────────────────────────┘Performance: Binding Modes Comparison
Scenario: Displaying a user's name in 100 places
┌────────────────────────────────────────────────────────┐
│ .one-time (set once) │
│ │
│ Initial Setup: Set value │
│ Updates: Never (even if value changes) │
│ Memory: ✅ Minimal (no observer) │
│ Performance: ✅✅✅ Fastest │
│ Use When: Static data that NEVER changes │
└────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────┐
│ .one-way / .to-view (display only) │
│ │
│ Initial Setup: Set value + Create observer │
│ Updates: When property changes │
│ Memory: ✅ Low (read-only observer) │
│ Performance: ✅✅ Fast │
│ Use When: Display data (most common) │
└────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────┐
│ .two-way (read and write) │
│ │
│ Initial Setup: Set value + Create bidirectional obs │
│ Updates: View ↔ ViewModel sync │
│ Memory: ⚠️ Higher (two-way observer) │
│ Performance: ⚠️ Slower │
│ Use When: Form inputs only │
└────────────────────────────────────────────────────────┘
Memory Usage Comparison (100 bindings):
.one-time: ▓░░░░░░░░░ 10% (no observers)
.one-way: ▓▓▓░░░░░░░ 30% (one-way observers)
.two-way: ▓▓▓▓▓░░░░░ 50% (two-way observers)Computed Properties Reactivity
View Model:
┌────────────────────────────────────┐
│ items = [ │
│ { price: 10, qty: 2 }, │
│ { price: 20, qty: 1 } │
│ ] │
│ │
│ get total() { │
│ return this.items.reduce( │
│ (sum, item) => │
│ sum + item.price * item.qty,│
│ 0 │
│ ); │
│ } │
└────────────────────────────────────┘
View:
┌────────────────────────────────────┐
│ <p>Total: ${total}</p> │
└────────────────────────────────────┘
What Aurelia Tracks:
┌────────────────────────────────────┐
│ 1. Accesses to 'items' array │
│ 2. Accesses to each item.price │
│ 3. Accesses to each item.qty │
└────────────────────────────────────┘
When These Change:
┌────────────────────────────────────┐
│ items.push(newItem) │
│ ↓ │
│ Aurelia detects change │
│ ↓ │
│ Re-runs get total() │
│ ↓ │
│ Updates view with new total │
└────────────────────────────────────┘
Reactive Change Flow:
items[0].price = 15
↓
Dirty checking detects change
↓
Re-evaluate all getters that accessed items[0].price
↓
Update DOM with new computed valueRelated Documentation
Last updated
Was this helpful?