{"record":{"id":"9242660230124b53","repo":"angular/components","slug":"incorrect-tree-structure-containing-detached-node","errorCode":null,"errorMessage":"Incorrect tree structure containing detached node.","messagePattern":"Incorrect tree structure containing detached node\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cdk/tree/tree.ts","lineNumber":1500,"sourceCode":"    }\n    this._shouldFocus = false;\n    this._tree._keyManager.focusItem(this);\n    this._shouldFocus = true;\n  }\n\n  _emitExpansionState(expanded: boolean) {\n    this.expandedChange.emit(expanded);\n  }\n}\n\nfunction getParentNodeAriaLevel(nodeElement: HTMLElement): number {\n  let parent = nodeElement.parentElement;\n  while (parent && !isNodeElement(parent)) {\n    parent = parent.parentElement;\n  }\n  if (!parent) {\n    if (typeof ngDevMode === 'undefined' || ngDevMode) {\n      throw Error('Incorrect tree structure containing detached node.');\n    } else {\n      return -1;\n    }\n  } else if (parent.classList.contains('cdk-nested-tree-node')) {\n    return numberAttribute(parent.getAttribute('aria-level')!);\n  } else {\n    // The ancestor element is the cdk-tree itself\n    return 0;\n  }\n}\n\nfunction isNodeElement(element: HTMLElement) {\n  const classList = element.classList;\n  return !!(classList?.contains('cdk-nested-tree-node') || classList?.contains('cdk-tree'));\n}\n","sourceCodeStart":1482,"sourceCodeEnd":1516,"githubUrl":"https://github.com/angular/components/blob/0411926e7d8ae06b32236ec1048a888cfad5abf2/src/cdk/tree/tree.ts#L1482-L1516","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst level = tree.getTreeHierarchyLevel(detachedNodeEl);\n// after\nif (detachedNodeEl.isConnected && treeEl.contains(detachedNodeEl)) {\n  const level = tree.getTreeHierarchyLevel(detachedNodeEl);\n}","handlingStrategy":"type-guard","validationCode":"if (!nodeElement.isConnected || !treeHost.contains(nodeElement)) {\n  return; // skip level computation for detached nodes\n}\nconst level = tree.getTreeHierarchyLevel(nodeElement);","typeGuard":"function isAttachedToTree(node: Element, treeHost: Element): boolean {\n  return node.isConnected && treeHost.contains(node);\n}","tryCatchPattern":"try {\n  const level = tree.getTreeHierarchyLevel(el);\n} catch (e) {\n  if (String((e as Error).message).includes('detached node')) return -1;\n  throw e;\n}","preventionTips":["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."],"tags":["angular","cdk","tree","dom-structure","dev-mode"],"backgroundTag":"detached-dom-node","analyzedSha":"0411926e7d8ae06b32236ec1048a888cfad5abf2","analyzedAt":"2026-08-31T11:58:23.400Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}