import { DOCUMENT, NgComponentOutlet, isPlatformBrowser } from '@angular/common'; import { ChangeDetectionStrategy, Component, Injector, PLATFORM_ID, computed, effect, inject, viewChild, } from '@angular/core'; import { MODAL_DATA, ModalRef, ModalService } from '../../../core/services/modal.service'; import { ModalShellComponent } from '../modal-shell/modal-shell.component'; @Component({ selector: 'app-modal-host', imports: [NgComponentOutlet, ModalShellComponent], templateUrl: './modal-host.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class ModalHostComponent { private readonly modalService = inject(ModalService); private readonly injector = inject(Injector); private readonly document = inject(DOCUMENT); private readonly platformId = inject(PLATFORM_ID); private readonly modalShell = viewChild(ModalShellComponent); private readonly isBrowser = isPlatformBrowser(this.platformId); readonly activeModal = this.modalService.activeModal; protected readonly contentInjector = computed(() => { const modal = this.activeModal(); if (!modal) { return undefined; } return Injector.create({ providers: [ { provide: ModalRef, useValue: modal.ref }, { provide: MODAL_DATA, useValue: modal.config.data ?? null }, ], parent: this.injector, }); }); constructor() { effect((onCleanup) => { const modal = this.activeModal(); if (!this.isBrowser || !modal) { return; } const body = this.document.body; const previousOverflow = body.style.overflow; const previousPaddingRight = body.style.paddingRight; const view = this.document.defaultView; const scrollbarWidth = view ? Math.max(0, view.innerWidth - this.document.documentElement.clientWidth) : 0; if (scrollbarWidth > 0 && view) { const currentPaddingRight = Number.parseFloat(view.getComputedStyle(body).paddingRight) || 0; body.style.paddingRight = `${currentPaddingRight + scrollbarWidth}px`; } body.style.overflow = 'hidden'; const onKeyDown = (event: KeyboardEvent) => { if (event.key !== 'Escape' || !this.activeModal()?.config.closeOnEscape) { return; } event.preventDefault(); this.activeModal()?.ref.dismiss('escape'); }; this.document.addEventListener('keydown', onKeyDown); queueMicrotask(() => this.modalShell()?.focusInitialElement()); onCleanup(() => { this.document.removeEventListener('keydown', onKeyDown); body.style.overflow = previousOverflow; body.style.paddingRight = previousPaddingRight; }); }); } protected onBackdropClick(): void { const modal = this.activeModal(); if (modal?.config.closeOnBackdrop) { modal.ref.dismiss('backdrop'); } } protected onCloseRequested(): void { this.activeModal()?.ref.dismiss('programmatic'); } }