angular/components · error
Incorrect tree structure containing detached node.
Error message
Incorrect tree structure containing detached node.
What it means
When computing the aria-level of a nested tree node, Angular Material walks up the DOM from a node element looking for an enclosing node element. If no ancestor node element is found, the node is detached from the tree's DOM structure and the code throws this error in dev mode (returns -1 in production). It signals a malformed or detached tree hierarchy.
Source
Thrown at src/cdk/tree/tree.ts:1500
}
this._shouldFocus = false;
this._tree._keyManager.focusItem(this);
this._shouldFocus = true;
}
_emitExpansionState(expanded: boolean) {
this.expandedChange.emit(expanded);
}
}
function getParentNodeAriaLevel(nodeElement: HTMLElement): number {
let parent = nodeElement.parentElement;
while (parent && !isNodeElement(parent)) {
parent = parent.parentElement;
}
if (!parent) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
throw Error('Incorrect tree structure containing detached node.');
} else {
return -1;
}
} else if (parent.classList.contains('cdk-nested-tree-node')) {
return numberAttribute(parent.getAttribute('aria-level')!);
} else {
// The ancestor element is the cdk-tree itself
return 0;
}
}
function isNodeElement(element: HTMLElement) {
const classList = element.classList;
return !!(classList?.contains('cdk-nested-tree-node') || classList?.contains('cdk-tree'));
}
View on GitHub (pinned to 0411926e7d)
Solutions
- Ensure tree nodes are rendered only inside the CdkTree/NestedTree DOM.
- Avoid manually reparenting or moving node elements with native DOM APIs.
- Re-render the tree rather than reusing detached node elements.
- Wrap inspection logic to check node connectivity before reading levels.
Example fix
// before
const level = tree.getTreeHierarchyLevel(detachedNodeEl);
// after
if (detachedNodeEl.isConnected && treeEl.contains(detachedNodeEl)) {
const level = tree.getTreeHierarchyLevel(detachedNodeEl);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (!nodeElement.isConnected || !treeHost.contains(nodeElement)) {
return; // skip level computation for detached nodes
}
const level = tree.getTreeHierarchyLevel(nodeElement); Type guard
function isAttachedToTree(node: Element, treeHost: Element): boolean {
return node.isConnected && treeHost.contains(node);
} Try / catch
try {
const level = tree.getTreeHierarchyLevel(el);
} catch (e) {
if (String((e as Error).message).includes('detached node')) return -1;
throw e;
} Prevention
- Never manually reparent or move tree node elements with native DOM APIs.
- Render nodes only inside the tree container (avoid portals for nodes).
- Guard DOM inspection code with isConnected checks.
- Re-render the tree instead of reusing removed node elements.
When it happens
Trigger: Calling getTreeHierarchyLevel (via levelAria or nested tree level computation) for a node element whose parentElement chain contains no element matching isNodeElement — e.g. a node rendered outside the tree container or removed from the DOM.
Common situations: Manually moving/reattaching node DOM elements; rendering tree nodes outside the tree via portals; test setups that query detached elements; timing issues where nodes are inspected after teardown.
Related errors
- Invalid DOM structure for drop list. Alternate container ele
- Invalid DOM structure for drop list. All items must be place
- Tree is using conflicting node types which can cause unexpec
- ListKeyManager constructed with a signal must receive an inj
- KeyManager items in typeahead mode must implement the `getLa
AI-assisted analysis of angular/components@0411926e7d (2026-08-31).
Data as JSON: /api/errors/9242660230124b53.
Report an issue: GitHub.