From 2ed38acea7d461d0b1e11233f6f5525ceec7c7c4 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 20 Feb 2026 13:33:02 +0200 Subject: [PATCH] fix(dynamic-dialog): fix overlay container scoping and lifecycle management - Scope DynamicOverlayContainer and related providers to an isolated child injector created per DynamicMatDialog instance, preventing shared state leaks across multiple dialog usages - Introduce PARENT_OVERLAY_CONTAINER token so DynamicOverlayContainer can fall back to the global overlay container when no custom element is set - Replace _containerElement assignment with a local _customElement field and override getContainerElement() for clean delegation logic - Simplify open() method using try/finally to ensure the container element is always reset after dialog creation, regardless of errors --- .../dialog/dynamic/dynamic-dialog.module.ts | 39 ++++++++++++------- .../dialog/dynamic/dynamic-dialog.ts | 19 +++------ .../dynamic/dynamic-overlay-container.ts | 16 ++++++-- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-dialog.module.ts b/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-dialog.module.ts index 078234dbda..50d847609f 100644 --- a/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-dialog.module.ts +++ b/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-dialog.module.ts @@ -15,36 +15,47 @@ /// import { Overlay, OverlayContainer, OverlayModule } from '@angular/cdk/overlay'; -import { NgModule } from '@angular/core'; +import { inject, Injector, NgModule } from '@angular/core'; import { DEFAULT_DIALOG_CONFIG, Dialog, DialogConfig, DialogModule } from '@angular/cdk/dialog'; import { MatDialogModule } from '@angular/material/dialog'; import { DynamicDialog, DynamicMatDialog } from './dynamic-dialog'; import { DynamicOverlay } from './dynamic-overlay'; -import { DynamicOverlayContainer } from './dynamic-overlay-container'; +import { DynamicOverlayContainer, PARENT_OVERLAY_CONTAINER } from './dynamic-overlay-container'; export const DYNAMIC_MAT_DIALOG_PROVIDERS = [ - DynamicOverlayContainer, - { provide: OverlayContainer, useExisting: DynamicOverlayContainer }, - DynamicOverlay, - { provide: Overlay, useExisting: DynamicOverlay }, - DynamicDialog, - { provide: Dialog, useExisting: DynamicDialog }, - DynamicMatDialog, { - provide: DEFAULT_DIALOG_CONFIG, - useValue: { - ...new DialogConfig() + provide: DynamicMatDialog, + useFactory: () => { + const parentInjector = inject(Injector); + const parentOverlayContainer = parentInjector.get(OverlayContainer); + + const customInjector = Injector.create({ + providers: [ + { provide: PARENT_OVERLAY_CONTAINER, useValue: parentOverlayContainer }, + DynamicOverlayContainer, + { provide: OverlayContainer, useExisting: DynamicOverlayContainer }, + DynamicOverlay, + { provide: Overlay, useExisting: DynamicOverlay }, + DynamicDialog, + { provide: Dialog, useExisting: DynamicDialog }, + DynamicMatDialog, + { provide: DEFAULT_DIALOG_CONFIG, useValue: new DialogConfig() } + ], + parent: parentInjector + }); + + return customInjector.get(DynamicMatDialog); } } ]; -@NgModule( { +@NgModule({ imports: [ OverlayModule, DialogModule, MatDialogModule ], providers: DYNAMIC_MAT_DIALOG_PROVIDERS -} ) +}) export class DynamicMatDialogModule { } diff --git a/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-dialog.ts b/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-dialog.ts index f717ccb0a7..df707ad49d 100644 --- a/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-dialog.ts +++ b/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-dialog.ts @@ -34,20 +34,13 @@ export class DynamicMatDialog extends MatDialog { config.containerElement.style.transform = 'translateZ(0)'; this._customOverlay.setContainerElement(config.containerElement); } - const ref = super.open(component, config); - if (config?.containerElement) { - ref.afterClosed().subscribe( - { - next: () => { - this._customOverlay.setContainerElement(null); - }, - error: () => { - this._customOverlay.setContainerElement(null); - } - } - ); + try { + return super.open(component, config); + } finally { + if (config?.containerElement) { + this._customOverlay.setContainerElement(null); + } } - return ref; } } diff --git a/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-overlay-container.ts b/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-overlay-container.ts index 83feda56fc..99a84f5a6a 100644 --- a/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-overlay-container.ts +++ b/ui-ngx/src/app/shared/components/dialog/dynamic/dynamic-overlay-container.ts @@ -15,13 +15,21 @@ /// import { OverlayContainer } from "@angular/cdk/overlay"; -import { Injectable } from "@angular/core"; +import { inject, Injectable, InjectionToken } from "@angular/core"; + +export const PARENT_OVERLAY_CONTAINER = new InjectionToken('PARENT_OVERLAY_CONTAINER'); @Injectable() export class DynamicOverlayContainer extends OverlayContainer { - public setContainerElement( containerElement:HTMLElement ):void { + private _globalContainer = inject(PARENT_OVERLAY_CONTAINER); + private _customElement: HTMLElement | null = null; + + public override getContainerElement(): HTMLElement { + return this._customElement || this._globalContainer.getContainerElement(); + } - this._containerElement = containerElement; + setContainerElement(element: HTMLElement | null): void { + this._customElement = element; } -} +} \ No newline at end of file