Angular. Вставить компонент в body

Функциональность требуется для диалогов, уведомлений и других элементов наложения. Данная реализация манипулирует только одним компонентом в один момент времени. Позволяет вставить компонент с нужными Input()ами.

Вариант добавления вручную

@Injectable({
  providedIn: 'root',
})
export class DomService {
  /** ref to window document */
  private readonly document: Document;
  /** renderer instance */
  private readonly renderer: Renderer2;
  /** attached component  */
  private componentRef: ComponentRef<unknown>;

  constructor(
    @Inject(DOCUMENT) document,
    private componentFactoryResolver: ComponentFactoryResolver,
    private injector: Injector,
    private applicationRef: ApplicationRef,
    rendererFactory: RendererFactory2
  ) {
    this.renderer = rendererFactory.createRenderer(null, null);
    this.document = document;
  }

  attachComponent<T>(component: Type<T>, componentProps: object = null): T {
    const componentRef = this.componentFactoryResolver
      .resolveComponentFactory(component)
      .create(this.injector);
    if (componentProps !== null && typeof componentRef.instance === 'object') {
      Object.assign(componentRef.instance, componentProps);
    }
    // put inside the angular component tree
    this.applicationRef.attachView(componentRef.hostView);
    const componentRootNode = (componentRef.hostView as EmbeddedViewRef<
      unknown
    >).rootNodes[0] as HTMLElement;
    // append component to the body
    this.renderer.appendChild(this.document.body, componentRootNode);
    this.componentRef = componentRef;
    return componentRef.instance;
  }

  /**
   * Destroy component
   */
  removeComponent(): void {
    this.applicationRef.detachView(this.componentRef.hostView);
    this.componentRef.destroy();
  }
}

Вариант добавления с помощью CDK Portal

@Injectable({
  providedIn: 'root',
})
export class CdkDomService {
  /** ref to window document slot */
  private bodyPortalOutlet: DomPortalOutlet;

  constructor(
    @Inject(DOCUMENT) document,
    componentFactoryResolver: ComponentFactoryResolver,
    injector: Injector,
    applicationRef: ApplicationRef
  ) {
    this.bodyPortalOutlet = new DomPortalOutlet(
      document.body,
      componentFactoryResolver,
      applicationRef,
      injector
    );
  }

  attachComponent<T>(component: ComponentType<T>, componentProps: object = null): T {
    const componentPortal = new ComponentPortal<T>(component);
    const componentRef = this.bodyPortalOutlet.attach<T>(componentPortal);
    if (componentProps !== null && typeof componentRef.instance === 'object') {
      Object.assign(componentRef.instance, componentProps);
    }
    return componentRef.instance;
  }

  /**
   * Destroy component
   */
  removeComponent(): void {
    this.bodyPortalOutlet.detach();
  }
}

Похожие записи

RxJS Pipeable Operators

Начиная с версии rxjs 5.5 операторы вместо цепочки вызовов применяются как параметры функции pipe.

Angular & MVVM

  • Model - just file like user.class.ts
  • View - HTML template of component
  • ViewModel - Typescript part of a component
14 апреля 2019 г. в Angular

Angular Let Directive

*ngIf не отображает содержимое в falsy случаях (0, null, undefined) на async pipe, в пакете @rx-angular/template предлагается решение

27 сентября 2020 г. в Angular

Вложенные формы Angular

Рассматриваются варианты встраивания форм, позволяющие переиспользовать набор полей вводе со своей логикой. @Input() родительской формы, @Output() дочерней формы, ViewChild(), ControlValueAccessor, ControlContainer