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 docs2. 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:
Activation is top-down until attached (which is bottom-up)
Deactivation is bottom-up throughout
binding() blocks children, attaching() doesn't
Stacks coordinate timing between parent and children
Always clean up in the opposite hook (attached ↔ detaching)
Use attached() for DOM work, not binding()
For more details, see the main Component Lifecycles documentation.
Last updated
Was this helpful?