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

  1. Ensure tree nodes are rendered only inside the CdkTree/NestedTree DOM.
  2. Avoid manually reparenting or moving node elements with native DOM APIs.
  3. Re-render the tree rather than reusing detached node elements.
  4. 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

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


AI-assisted analysis of angular/components@0411926e7d (2026-08-31). Data as JSON: /api/errors/9242660230124b53. Report an issue: GitHub.