Miguel Moura
Miguel Moura

Reputation: 39364

Add child component to parent component and add parent to document

In an Angular 7 modal service I am creating a modal Stackblitz.

The modal is simply a DIV added to document as follows:

const modal = document.createElement('div');
modal.classList.add('modal');
modal.appendChild(this.element);

document.body.appendChild(this.modal);

The modal content (this.element) is a component created dynamically.

What I would like is modal to be also a Component added dynamically:

  1. Create modal from a ModalComponent (I think similar to 2);
  2. Create element component (DONE);
  3. Add element component as child of modal component;
  4. Add modal component to document.

Could someone help me with (3) and (4)? Not sure how to do it.

Modal

export class Modal {
  protected modal: any = null;
  close() {
    this.modal.close();
  }
}

ModalService

import { ApplicationRef, ComponentFactoryResolver, EmbeddedViewRef, Injectable, Injector, ComponentRef, ReflectiveInjector } from '@angular/core';

@Injectable({
  providedIn: 'root'
})

export class ModalService {

  private component: ComponentRef<any>;
  private element: any;
  private stage: any;

  constructor(private componentFactoryResolver: ComponentFactoryResolver, private application: ApplicationRef, private injector: Injector) { }

  open(component: any, data: any): void {

    if(this.element) 
      return;

    const injector: Injector = ReflectiveInjector.resolveAndCreate([{ provide: 'modal', useValue: data }]);

    this.component = this.componentFactoryResolver.resolveComponentFactory(component).create(injector);

    this.component.instance.modal = this;

    this.application.attachView(this.component.hostView);

    this.element = (this.component.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;

    const modal = document.createElement('div');
    modal.classList.add('modal');
    modal.appendChild(this.element);

    document.body.appendChild(this.modal);

  }

  close(): void {

    this.application.detachView(this.component.hostView);
    this.stage.parentNode.removeChild(this.stage);
    this.component.destroy();
    this.element = null;

  }

}

Upvotes: 0

Views: 1549

Answers (1)

yurzui
yurzui

Reputation: 214017

First you need to dynamically create that inner component and then use projectableNodes parameter in ComponentFactory.create method to project it into your dynamically created ModalComponent.

The ModalComponent is supposed to have ng-content tag in its template:

<div class="stage">
  <div class="modal">
    <ng-content></ng-content>
  </div>
</div>

modal.service.ts

open(component: Type<Modal>): void {
  if (this.modalRef)
    return;

  this.elementRef = this.componentFactoryResolver
    .resolveComponentFactory(component)
    .create(this.injector);
  this.appRef.attachView(this.elementRef.hostView);
  this.elementRef.instance.modal = this;

  this.modalRef = this.componentFactoryResolver
    .resolveComponentFactory(ModalComponent)
    .create(this.injector, [[this.elementRef.location.nativeElement]]);
                                         \/
                      here we're projecting inner component into modal

  this.appRef.attachView(this.modalRef.hostView);

  document.body.appendChild(this.modalRef.location.nativeElement);
}


close(): void {
  this.appRef.detachView(this.elementRef.hostView);
  this.elementRef.destroy();
  this.elementRef = null;

  this.appRef.detachView(this.modalRef.hostView);
  this.modalRef.destroy();
  this.modalRef = null;
}

Forked Stackblitz

Upvotes: 3

Related Questions