Lifecycle Visual Diagrams

Visual explanations of Aurelia 2's component lifecycle with parent-child timing.

Table of Contents


1. Activation Sequence (Parent-Child)

How lifecycle hooks execute when activating a parent with children:

SCENARIO: Parent component with 2 children activates
═══════════════════════════════════════════════════════════════

Timeline:
─────────────────────────────────────────────────────────────

Time  Parent              Child-1            Child-2
────  ──────              ───────            ───────
  0   constructor()
  1   define()
  2   hydrating()
  3   hydrated()
      created() ←──────── created() ←─────── created()
                          │                  │
                          └─ children first ─┘

  4   binding() ──────┐
      ↓ (if async,    │
      blocks children)│

  5   ← resolve ──────┘
      bind() (connects bindings)

  6   attaching() ────┐
      _attach() DOM ──┤  binding() ────┐   binding() ────┐
                      │                │                  │
                      │  bind()        │   bind()         │
                      │                │                  │
                      │  attaching() ──┤   attaching() ───┤
                      │  _attach() DOM │   _attach() DOM  │
                      │                │                  │
                  ┌───┴────────────────┴──────────────────┘
                  │   (parent's attaching() and
                  │    children activation run in PARALLEL)

  7               └─→ Wait for all to complete

      attached() ←───── attached() ←──── attached()
      │                 │                 │
      └─ children first (bottom-up) ─────┘

  8   ACTIVATED


DETAILED ACTIVATION FLOW
═══════════════════════════════════════════════════════════

┌────────────────────────────────────────────────────┐
│ CONSTRUCTION PHASE (Top ➞ Down)                    │
├────────────────────────────────────────────────────┤
│                                                    │
│ Parent.constructor()                               │
│   → Child1.constructor()                           │
│   → Child2.constructor()                           │
│                                                    │
│ Parent.define()                                    │
│   → Child1.define()                                │
│   → Child2.define()                                │
│                                                    │
│ Parent.hydrating()                                 │
│   → Child1.hydrating()                             │
│   → Child2.hydrating()                             │
│                                                    │
│ Parent.hydrated()                                  │
│   → Child1.hydrated()                              │
│   → Child2.hydrated()                              │
│                                                    │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│ CREATED PHASE (Bottom ➞ Up)                        │
├────────────────────────────────────────────────────┤
│                                                    │
│   Child1.created()                                 │
│   Child2.created()                                 │
│     → Parent.created()  ← After all children      │
│                                                    │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│ BINDING PHASE (Top ➞ Down, Blocks Children)       │
├────────────────────────────────────────────────────┤
│                                                    │
│ Parent.binding()                                   │
│   ↓ (if async, children wait)                     │
│   ↓                                                │
│ [ await parent.binding() ]                         │
│   ↓                                                │
│ Parent.bind() - connects bindings to scope        │
│   ↓                                                │
│   Child1.binding()                                 │
│     ↓                                              │
│   [ await child1.binding() ]                       │
│     ↓                                              │
│   Child1.bind()                                    │
│                                                    │
│   Child2.binding()                                 │
│     ↓                                              │
│   [ await child2.binding() ]                       │
│     ↓                                              │
│   Child2.bind()                                    │
│                                                    │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│ BOUND PHASE (Bottom ➞ Up)                          │
├────────────────────────────────────────────────────┤
│                                                    │
│   Child1.bound()                                   │
│   Child2.bound()                                   │
│     → Parent.bound()  ← After children             │
│                                                    │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│ ATTACHING PHASE (Parallel!)                        │
├────────────────────────────────────────────────────┤
│                                                    │
│ Parent.attaching()         activatingStack = 1    │
│   ↓                              ↓                 │
│ Parent._attach()                 │                 │
│   → Append to DOM                │                 │
│                                  │                 │
│ [ Both run in PARALLEL ]         │                 │
│   ├─ await parent.attaching()    │                 │
│   └─ Child activation ───────────┘                 │
│        ├─ Child1.binding()       activatingStack++ │
│        ├─ Child1.bind()                            │
│        ├─ Child1.bound()                           │
│        ├─ Child1.attaching()                       │
│        ├─ Child1._attach()                         │
│        │    → Append to DOM                        │
│        │                                           │
│        ├─ Child2.binding()       activatingStack++ │
│        ├─ Child2.bind()                            │
│        ├─ Child2.bound()                           │
│        ├─ Child2.attaching()                       │
│        └─ Child2._attach()                         │
│             → Append to DOM                        │
│                                                    │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│ ATTACHED PHASE (Bottom ➞ Up)                       │
├────────────────────────────────────────────────────┤
│                                                    │
│ _leaveActivating() called on each child           │
│   activatingStack-- for each                      │
│                                                    │
│ When activatingStack === 0:                        │
│   Child1.attached()            activatingStack--  │
│   Child2.attached()            activatingStack--  │
│     → Parent.attached()        activatingStack--  │
│                                ↓                   │
│                           activatingStack === 0   │
│                           → state = activated     │
│                                                    │
└────────────────────────────────────────────────────┘


KEY IMPLEMENTATION DETAILS
═══════════════════════════════════════════════════════════

1. _enterActivating() increments activatingStack
   - Called when starting binding phase
   - Recursively increments parent's stack

2. Parent's attaching() runs in PARALLEL with children
   - Children start activating while parent is still attaching
   - This allows for better performance

3. attached() only called when stack === 0
   - _leaveActivating() decrements stack
   - When stack reaches 0, attached() is invoked
   - This ensures bottom-up execution

4. binding() can block
   - If it returns a Promise, children wait
   - This is why it's marked "blocks children" in docs

2. Deactivation Sequence (Parent-Child)

How lifecycle hooks execute when deactivating:


3. Stack-Based Coordination

How Aurelia ensures correct timing using activation/deactivation stacks:


4. Async Lifecycle Behavior

How async hooks affect timing:


5. Common Pitfalls

Real-world mistakes and how to avoid them:


Summary

Key Takeaways:

  1. Activation is top-down until attached (which is bottom-up)

  2. Deactivation is bottom-up throughout

  3. binding() blocks children, attaching() doesn't

  4. Stacks coordinate timing between parent and children

  5. Always clean up in the opposite hook (attached ↔ detaching)

  6. Use attached() for DOM work, not binding()

For more details, see the main Component Lifecycles documentation.

Last updated

Was this helpful?