Как лучше предотвратить потерю данных в модальном окне при использовании content projection в Angular?

Ссылка скопирована
1 ответ

Сейчас ситуация такая: при реализации модального окна с использованием ng-content (content projection) возникает критичная проблема: контент переданный в неё затирается. Сейчас модалка сразу же открывается при входе в приложение и только в этот момент данные отображаются(для дебага)

Как должно работать:
Пользователь жмет кнопку
По вводным: открывается модалка с контентом переданным в нее(любая форма)

По вводным: суть в том, что через ng-content можно было бы отправлять в модалку компоненты или html, эдакий конструктор.

Реализация сейчас:
Service:

export class ModalService { containerModal?: ViewContainerRef; modalRef?: ComponentRef<any>; registerModalContainer(container: ViewContainerRef): void { this.containerModal = container; } show(component: any) { if (!this.containerModal) return; this.modalRef = this.containerModal.createComponent(component); } destroy(): void { if (this.modalRef) { this.modalRef.destroy(); } } }

Модалка:

export class ModalService { containerModal?: ViewContainerRef; modalRef?: ComponentRef<any>; registerModalContainer(container: ViewContainerRef): void { this.containerModal = container; } show(component: any) { if (!this.containerModal) return; this.modalRef = this.containerModal.createComponent(component); } destroy(): void { if (this.modalRef) { this.modalRef.destroy(); } } }

@if (close()){ <div class="overlay"> <div class="window_modal" > <button class="clear_btn" (click)="onClose()"> <svg icon="cross" class="svg10"></svg> </button> <ng-content> </ng-content> </div> </div>}

Host:

@Component({ selector: 'tt-host-modal', imports: [], templateUrl: `/host-modal.component.html`, styleUrl: './host-modal.component.scss', }) export class HostModalComponent implements AfterViewInit, OnDestroy { @ViewChild('modalHost', { read: ViewContainerRef }) containerRef?: ViewContainerRef; modalService = inject(ModalService); ngAfterViewInit(): void { if (!this.containerRef) {return;} this.modalService.registerModalContainer(this.containerRef) } ngOnDestroy(): void { this.modalService.destroy(); }}

<ng-template #modalHost></ng-template>
Страница с модалкой:

@Component({ selector: 'tt-community-page', imports: [ CommunityCardComponent, CommunityFiltersComponent, SvgIconComponent, InfiniteScrollTriggerComponent, WindowModalComponent, ], templateUrl: './community-page.component.html', styleUrl: './community-page.component.scss', }) export class CommunityPageComponent { #store = inject(Store); modalService = inject(ModalService); communities$ = this.#store.selectSignal(communityFeature.selectCommunityFiltered); timeToFetch() { this.#store.dispatch(filtercommunityActions.setPage({ page: 1 })); } createCommunityModal(): void { this.modalService.show(WindowModalComponent) } } <code lang="html"> <div class="btn-wrapper"> <h1>Cообщества</h1> <button class="btn" (click)="createCommunityModal()">Создать сообщество <svg icon="content" class="svg16 "></svg> </button> </div> <tt-window-modal> Habr </tt-window-modal> <tt-community-filters></tt-community-filters> <div class="wrapper-for"> @for (community of communities$(); track community.id; let last = $last) { <tt-community-card [community]="community"></tt-community-card> @if (last) { @defer (on viewport) { <tt-infinite-scroll-trigger (loaded)="timeToFetch()" ></tt-infinite-scroll-trigger> } @placeholder { <div></div> } } } </div> </code>
Нужно решить такую задачу?

Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.

Заказать помощь
Лучший ответ
1
Антон С. Ответ

В Angular проблема в том, что ng-content не работает как “передать произвольный контент в динамически созданный компонент” само по себе. Content projection привязан к шаблону родительского компонента на этапе компиляции. Если вы создаёте модалку через ViewContainerRef.createComponent(), контент, который должен попасть внутрь, нужно передавать явно: через component, TemplateRef, ngTemplateOutlet или projectableNodes.

Самый простой и управляемый вариант для форм — открывать модалку с TemplateRef:

&lt;ng-template #formTemplate&gt;
  &lt;app-user-form&gt;&lt;/app-user-form&gt;
&lt;/ng-template&gt;
 
&lt;button (click)="open(formTemplate)"&gt;Открыть&lt;/button&gt;

&lt;ng-template #formTemplate&gt; &lt;app-user-form&gt;&lt;/app-user-form&gt; &lt;/ng-template&gt; &lt;button (click)="open(formTemplate)"&gt;Открыть&lt;/button&gt;

open(template: TemplateRef) {
  this.modalService.open(template);
}

open(template: TemplateRef) { this.modalService.open(template); }

А внутри модального компонента:

&lt;div class="modal"&gt;
  &lt;ng-container *ngTemplateOutlet="template"&gt;&lt;/ng-container&gt;
&lt;/div&gt;

&lt;div class="modal"&gt; &lt;ng-container *ngTemplateOutlet="template"&gt;&lt;/ng-container&gt; &lt;/div&gt;

В сервисе храните ссылку на template и передавайте её в instance созданной модалки:

show(template: TemplateRef) {
  const ref = this.containerModal.createComponent(ModalComponent);
  ref.instance.template = template;
  this.modalRef = ref;
}

show(template: TemplateRef) { const ref = this.containerModal.createComponent(ModalComponent); ref.instance.template = template; this.modalRef = ref; }

Если нужно открывать именно компонент, а не шаблон, то передавайте класс компонента и данные через inputs:

const ref = this.containerModal.createComponent(UserFormComponent);
ref.setInput('userId', id);

const ref = this.containerModal.createComponent(UserFormComponent); ref.setInput('userId', id);

Ваш текущий подход создаёт компонент, но не сохраняет корректный projected content. Поэтому при закрытии/открытии данные могут затираться, а контент появляется только при стартовом дебаг-сценарии.

Итог: для конструктора модалок в Angular используйте TemplateRef + ngTemplateOutlet или динамическое создание конкретного компонента с inputs. Не рассчитывайте, что ng-content автоматически перенесёт форму в компонент, созданный через сервис.

Другие ответы (0)

Пока нет других ответов. Будьте первым, кто поможет автору.

Ответить на вопрос

комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Вам также может быть интересно