Lifecycle
REALM follows the native custom element lifecycle. The constructor does nothing. All setup runs when the element is connected to the page.
When an element connects
- A shadow root is created (once per element) and the component's stylesheets are adopted.
- The script runs for this instance.
host,signalandinternalsare ready.refsis still empty because the markup does not exist yet. - The markup is cloned, and every slot, binding, handler, condition, loop and fetch attribute is connected.
onConnectedcallbacks run.refsnow holds every element with arefattribute.
const editor = useState({ ready: false });
onConnected(() => {
refs.input.focus();
const observer = new ResizeObserver(() => measure());
observer.observe(host);
return () => observer.disconnect(); // runs when the element is removed
});A function returned from onConnected runs on removal.
refs is reactive, so an observe at the top of the script sees each ref as soon as the markup exists:
observe(() => {
if (refs.canvas) draw(refs.canvas);
});When an element is removed
Removal is handled one microtask later, so a move (remove, then insert again in the same task) is not a removal:
- Moved to another place in the page: the instance, its state and its DOM are kept.
- Removed:
onDisconnectedcallbacks run, then the element'ssignalaborts, every observer stops, every listener, timer, poll and request REALM created is cancelled, prop accessors and methods added withdefineMethodsare removed, and the shadow root is emptied. - Added again later: it mounts fresh, with new state, as if it were new.
onDisconnected(() => {
saveDraft();
});signal
signal is an AbortSignal that aborts when the element is removed. Pass it to anything you start yourself:
const response = await fetch("/api/report", { signal });
window.addEventListener("resize", measure, { signal });
const id = setInterval(poll, 5000);
signal.addEventListener("abort", () => clearInterval(id));The listen helper does this for listeners. For timers, return a cleanup function from observe.
host, refs and internals
| Helper | What it is |
|---|---|
host |
The element itself. Use it for host.dispatchEvent, host.getRootNode(), host.closest(...) |
refs |
An object of elements marked with ref="name" in the markup |
internals |
The element's ElementInternals: states, role, setFormValue, setValidity, form, labels |
internals.states drives custom CSS states:
observe(() => {
if (menu.open) internals.states.add("open");
else internals.states.delete("open");
});:host(:state(open)) .panel {
display: block;
}While a fetch attribute request runs inside a component, its host matches :state(loading).
Form lifecycle
Form-associated components also get onFormReset, onFormDisabled and onFormStateRestore. See Forms.
Elements inside definitions
Elements written inside a <custom-element> definition in the page are never mounted while they are part of the definition. They mount once the definition is used.