Summary
Component.onCompleted and Component.onDestruction are not normal signals attached to your object instances; they are special handler hooks implemented by the QML engine.
- The
Componenttype is not part of your object’s inheritance chain (e.g.,Rectangle->Item->QtObject). - The syntax
Component.onCompletedinstructs the QML engine to register a callback on the component instance managing your object, rather than connecting to a signal on the object itself. - This allows you to execute code at specific lifecycle points (construction and destruction) that are decoupled from the C++ property binding evaluation cycle.
- They are not documented in the inheritance lists because they do not exist as member properties of the object instances.
Root Cause
The root cause of this confusion lies in the difference between QML Type Metadata and Object Instance Members.
- Component Incubation: When the QML engine parses an object definition (like a
Rectangle), it creates an internal representation of the logic needed to instantiate that object. This is theComponent. - The “Component” Identifier: Within the scope of a single QML file, the identifier
Componentis a special token that refers to the current component definition being evaluated, not a generic base class. - Engine Hooks: The QML engine specifically looks for properties attached to the
Componentidentifier. If it findsonCompletedoronDestruction, it registers the enclosed code blocks as callbacks to be fired by the engine during the object’s lifecycle.
Consequently, the code inside Component.onCompleted executes after the Rectangle (and its children) are fully created, but strictly before the Rectangle is considered fully “active” in the scene.
Why This Happens in Real Systems
In modern UI development, separating initialization logic from object construction is vital for performance and stability.
- Dependency Resolution: An object often depends on its parent’s geometry or external data sources. If you run logic immediately inside a constructor or property initializer, those dependencies might not be ready.
- Asynchronous Incubation: QML supports asynchronous component loading.
Component.onCompletedprovides a guaranteed trigger point that fires only when the object is actually fully instantiated and ready for interaction, regardless of whether the loading was synchronous or asynchronous. - Safe Teardown:
Component.onDestructionallows for state cleanup (closing network sockets, saving transient UI state) that is distinct from the C++ destructor logic, preventing crashes caused by accessing already-destroyed child elements.
Real-World Impact
Failing to understand this mechanism leads to common architectural errors:
- Race Conditions: Attempting to access
parent.widthorsomeChild.enabledinside a standard property initializer often results inundefinedor0because the binding graph hasn’t finished resolving dependencies.Component.onCompletedguarantees the layout is resolved. - Memory Leaks: Relying solely on C++ style destructors without
Component.onDestructioncan leave dangling references in JavaScript closures or singletons, as the QML engine’s garbage collection might not trigger immediate C++ object deletion. - Performance Bottlenecks: Doing heavy initialization (database calls, complex calculations) in
onCompletedblocks the UI thread. Senior engineers know thatComponent.onCompletedis the correct place to trigger asynchronous loading, not to perform the heavy work synchronously.
Example or Code
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
// This Rectangle is the "root" object of this component
Rectangle {
id: rootRect
color: "red"
width: 100
height: 100
// This property binding is evaluated DURING construction
property int counter: 0
// This block is executed by the Engine AFTER construction is complete
Component.onCompleted: {
// We can safely access the object here
console.log("Rectangle created and ready. Size:", width, "x", height)
// This would be unsafe in a property binding initializer
// because 'rootRect' might not be fully parented yet.
parent.focus = true
}
Component.onDestruction: {
console.log("Rectangle is about to be removed from the tree.")
}
}
// To answer the "Can I define my own?" question:
// No. You cannot define a signal named 'Component.myHandler'
// and have the engine automatically call it.
// This mechanism is hardcoded for these two specific handlers.
}
How Senior Engineers Fix It
Senior engineers use Component.onCompleted strictly as a lifecycle trigger, not a generic constructor.
- State Initialization: Use it to set up initial state that relies on the object’s geometry or parent context.
- Event Subscription: Use it to subscribe to external services or signal sources that might fire immediately and need the object to be ready to receive them.
- Focus Management: Use it to explicitly request focus or perform visual animations that require the layout to be settled.
- Lazy Loading: Use it to kick off asynchronous loading of heavy resources, ensuring the UI becomes responsive first before loading data.
Why Juniors Miss It
Juniors often miss this distinction because they view QML purely as an Object-Oriented language where every method or signal belongs to a class.
- Syntax Deception: The dot notation
Component.onCompletedlooks exactly like accessing a static member or a property of an object, but it is actually a special language keyword. - Inheritance Search: They instinctively look for
onCompletedin theRectangleorItemdocumentation, expecting it to be an inherited method. They don’t realize it is a global identifier injected by the engine into the QML scope. - Lack of Engine Awareness: They don’t understand the incubation process. They treat the physical instantiation of a C++ object as the same moment as the QML component being “ready,” which are two distinct moments in time separated by the event loop.