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 automatically

From-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-time

Conditional 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 changes

show.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 toggles

Decision 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 case

Capturing 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 View

Converter Flow Detail

┌──────────────────┐
│   View Model     │
│   price = 299.99 │
└────────┬─────────┘


┌──────────────────┐
│ CurrencyConverter│
│ toView(299.99,   │
│   'USD')         │
│ → "$299.99 USD"  │
└────────┬─────────┘


┌──────────────────┐
│ TruncateConverter│
│ toView(          │
│   "$299.99 USD", │
│   10)            │
│ → "$299.99..."   │
└────────┬─────────┘


    Final Output

Component 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 value

Last updated

Was this helpful?