{"record":{"id":"897aa473c1eb7467","repo":"angular/components","slug":"attempting-to-detach-a-portal-that-is-not-attached","errorCode":null,"errorMessage":"Attempting to detach a portal that is not attached to a host","messagePattern":"Attempting to detach a portal that is not attached to a host","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cdk/portal/portal-errors.ts","lineNumber":57,"sourceCode":"    'Attempting to attach an unknown Portal type. BasePortalOutlet accepts either ' +\n      'a ComponentPortal or a TemplatePortal.',\n  );\n}\n\n/**\n * Throws an exception when attempting to attach a portal to a null host.\n * @docs-private\n */\nexport function throwNullPortalOutletError() {\n  throw Error('Attempting to attach a portal to a null PortalOutlet');\n}\n\n/**\n * Throws an exception when attempting to detach a portal that is not attached.\n * @docs-private\n */\nexport function throwNoPortalAttachedError() {\n  throw Error('Attempting to detach a portal that is not attached to a host');\n}\n","sourceCodeStart":39,"sourceCodeEnd":59,"githubUrl":"https://github.com/angular/components/blob/0411926e7d8ae06b32236ec1048a888cfad5abf2/src/cdk/portal/portal-errors.ts#L39-L59","documentation":"Thrown when detach() is called on a Portal that is not currently attached to any host. The Portal API tracks attachment state and refuses to detach something that was never attached (or was already detached), because there is no host view to remove it from.","triggerScenarios":"Calling portal.detach() before portal.attach(outlet) was ever called; calling detach() twice; calling detach() after the host outlet has been destroyed and attachment state was lost.","commonSituations":"Cleanup code in ngOnDestroy that unconditionally detaches; re-executing a teardown function; a component destroyed before its portal attach logic ever ran; sharing a portal instance across hosts without tracking which host it is attached to.","solutions":["Check attachment state first: only call detach() if the portal is attached (e.g. wrap in try/catch or track attached outlet yourself)","Guard cleanup with an attached flag: if (this.attached) { portal.detach(); this.attached = false; }","Use the outlet's detach() (DomPortalOutlet.detach / CdkPortalOutlet.detach) only after verifying it has an attached portal (outlet.hasAttached())","If using CdkPortalOutlet directive, prefer relying on its automatic cleanup on destroy instead of manual detach"],"exampleFix":"// before\nngOnDestroy() {\n  this.portal.detach(); // throws if never attached\n}\n// after\nngOnDestroy() {\n  if (this.outlet && this.outlet.hasAttached()) {\n    this.portal.detach();\n  }\n}","handlingStrategy":"validation","validationCode":"if (outlet?.hasAttached()) {\n  portal.detach();\n}","typeGuard":"function isAttached(portal: Portal<any>, outlet: PortalOutlet | undefined): boolean {\n  return !!outlet && outlet.hasAttached();\n}","tryCatchPattern":"try {\n  portal.detach();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('not attached')) {\n    // already detached; nothing to do\n  } else {\n    throw e;\n  }\n}","preventionTips":["Track attachment state in your component and only detach once","Wrap cleanup code so teardown is idempotent","Use outlet.hasAttached() before detaching","Let CdkPortalOutlet handle cleanup on destroy instead of manual detach calls"],"tags":["angular","cdk","portal","lifecycle"],"backgroundTag":"detach-not-attached","analyzedSha":"0411926e7d8ae06b32236ec1048a888cfad5abf2","analyzedAt":"2026-08-31T11:58:23.400Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}