Angular2 - 如何动态创建组件并附加到body的viewcontainer

Jul*_*ano 2 angular

我一直在尝试动态创建一个组件并将其附加到文档标记.我一直很难搞清楚如何选择正文的ViewContainterRef,所以我可以使用ComponentFactoryResolver添加一个新组件.

我尝试使用下面的代码获取对body容器的引用,但它不起作用.有谁知道怎么做?谢谢!

import {
    Component,
    ComponentRef,
    ApplicationRef,
    Injector,
    Input,
    ViewContainerRef,
    ComponentFactoryResolver,
    ViewChild,
    OnInit,
    OnDestroy
} from '@angular/core';

import {
    ModalComponent
} from './modal.component';

@Component({
    selector: 'my-modal'
})
export class MyModalComponent {

    private _bodyRef: ViewContainerRef;


    constructor(private resolver: ComponentFactoryResolver, private app: ApplicationRef) {

        // Does not work!
        this._bodyRef = app['_rootComponents'][0]['_hostElement'].vcRef;

    }


    ngOnInit() {

        // Calls the factory to crate a brand new instance
        let componentFactory = this.resolver.resolveComponentFactory(ModalComponent);
        this._bodyRef.createComponent(componentFactory);


    }
}
Run Code Online (Sandbox Code Playgroud)

Mil*_*lad 6

Angular2材料团队正在为工具提示和其他动态组件做类似的事情,视图可能在当前容器引用之外.

这是类:https://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts 他们在做什么:

首先注入一堆有用的类:

constructor(
      private _viewRef: ViewContainerRef,
      private _hostDomElement: Element,
      private _componentFactoryResolver: ComponentFactoryResolver,
      private _appRef: ApplicationRef,
      private _defaultInjector: Injector) {
  }
Run Code Online (Sandbox Code Playgroud)

然后:在当前viewContainerRef中创建组件,在您的情况下是您的模态.

ngOnInit() {

        // Calls the factory to crate a brand new instance
        let componentFactory = this._componentFactoryResolver.resolveComponentFactory(ModalComponent);
        let componentRef = this._viewRef.createComponent(componentFactory);


    }
Run Code Online (Sandbox Code Playgroud)

然后将其附加到appRef

 (this._appRef as any).attachView(componentRef.hostView);

        this.setDisposeFn(() => {
          (this._appRef as any).detachView(componentRef.hostView);
          componentRef.destroy();
        });
Run Code Online (Sandbox Code Playgroud)

我从来没有这样做过,所以你可能需要付出一些努力,但我认为这就是方法.


waw*_*wka 6

我用单独的服务解决了这个问题:

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

@Injectable()
export class DOMService {

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

  appendComponentToBody(component: any) {
    //create a component reference
    const componentRef = this.componentFactoryResolver.resolveComponentFactory(component)
      .create(this.injector);

    // attach component to the appRef so that so that it will be dirty checked.
    this.applicationRef.attachView(componentRef.hostView);

    // get DOM element from component
    const domElem = (componentRef.hostView as EmbeddedViewRef < any > )
      .rootNodes[0] as HTMLElement;

    document.body.appendChild(domElem);

    return componentRef;
  }

  removeComponentFromBody(componentRef: ComponentRef < any > ) {
    this.applicationRef.detachView(componentRef.hostView);
    componentRef.destroy();
  }
}
Run Code Online (Sandbox Code Playgroud)

然后在您的组件中:

import {
  Component,
  AfterContentInit
} from '@angular/core';
import {
  ComponentToInject
} from 'path/to/component';
import {
  DOMService
} from 'path/to/service';

@Component({
  selector: 'my-component',
  template: '....'
})
export class MyComponent implements AfterContentInit {

  constructor(
    private DOMService: DOMService,
  ) {}

  ngAfterContentInit() {
      // to prevent ExpressionChangedAfterItHasBeenCheckedError
      setTimeout(() => {
          const cmp = this.DOMService.appendComponentToBody(ComponentToInject);

          // if you need to get access to input of injected component. Let's say ComponentToInject has public property title
          const instance = cmp.instance;
          instance.title = 'Some title';

          // if you need to get access to output of injected component. Let's say ComponentToInject assings EventEmitter to onClick property
          instance.onClick.subscribe(() => { // do somethis })
          });
        }

      }
Run Code Online (Sandbox Code Playgroud)

  • 理想情况下,应该注入文档 - `constructor(@Inject(DOCUMENT) private document: Document)` (2认同)