Angular 2: Inserting capture element dynamically w

2019-01-25 21:19发布

My goal is to create a child component and insert into the parent component template. There are examples to do this. However, I create parent component template (DOM Elements) dynamically in the parent component while most of the examples shown statically create the template with the capture element.

Here's the code

app.component

import {Component, ViewChild, ViewContainerRef, ComponentFactoryResolver} from '@angular/core';
import {NewChildComponent} from "./newChild.component";

@Component({
 selector: 'app-main',
 templateUrl: 'app.component.html'
})
export class AppComponent {

 @ViewChild('captureElement', {read: ViewContainerRef})
 captureElement: ViewContainerRef;

 constructor (private componentFactoryResolver: ComponentFactoryResolver) {
 var childComponent = this.componentFactoryResolver.resolveComponentFactory(NewChildComponent); 

 var myArea = document.getElementById('myArea');
 var myRow = document.createElement("div");
 myArea.appendChild(myRow);

 //I want to add the capture element #myCapture as a child of myRow
 //Add something like this <div #captureElement></div> programmatically through JS/TS
 // How can I do this?

 // I then create the component
  this.parent.createComponent(NewChildComponent);

 }

app.component.html

<div id="myArea">
  <!-- Static way of doing it -->
  <!--<div #captureElement></div>-->      
</div>

Instead of statically defining in #captureElement where the child component would be inserted, I would like to create it dynamically in the parent component and make it a child component.

Here are a list of questions I referred before I asked this question

Tried a couple of things

  1. Tried to create a div element with a #captureElement as an attribute programmatically but that doesn't work.
  2. Tried to create a random element programmatically and use ViewContainerRef to find it. That doesn't work either.

1条回答
男人必须洒脱
2楼-- · 2019-01-25 21:51

We can't create a ViewContainerRef, as a ViewContainerRef only exists within a view.

In 2.3.0, attachView was introduced which allows you to be able to attach change detection to the ApplicationRef. You can create some class that will encapsulate your logic like:

export class HtmlContainer {
   private attached: boolean = false;

   private disposeFn: () => void;

   constructor(
    private hostElement: Element,
    private appRef: ApplicationRef,
    private componentFactoryResolver: ComponentFactoryResolver, 
    private injector: Injector) {
  }

  attach(component: Type<any>) : ComponentRef<any> {
    if(this.attached) {
      throw new Error('component has already been attached')
    }

    this.attached = true;
    const childComponentFactory = this.componentFactoryResolver.resolveComponentFactory(component);

    let componentRef = childComponentFactory.create(this.injector);

    this.appRef.attachView(componentRef.hostView);
    this.disposeFn = () => {
        this.appRef.detachView(componentRef.hostView);
        componentRef.destroy();
    };

    this.hostElement.appendChild((componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0]);

    return componentRef;
  }

  dispose() {
    if(this.attached) {
      this.disposeFn();
    }
  }
}

this class is just helper that

1) resolves your dynamic component

this.componentFactoryResolver.resolveComponentFactory(component);

2) then compiles component by calling compFactory.create

3) after that registers its changeDetector (componentRef.hostView extends ChangeDetectorRef) by calling mentioned above appRef.attachView (otherwise change detection won't work for your component)

4) and finally appends the rootNode of your component to the host element

this.hostElement.appendChild((componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0]);

You can use it as follows:

@Component({
  selector: 'my-app',
  template: `<div id="myArea"></div> `,
  entryComponents: [NewChildComponent]
})
export class AppComponent {
  containers: HtmlContainer[] = [];

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

  ngOnInit() {
    var myArea = document.getElementById('myArea');
    var myRow = document.createElement("div");
    myArea.appendChild(myRow);

    this.addComponentToRow(NewChildComponent, myRow, 'test1');
    this.addComponentToRow(NewChildComponent, myRow, 'test2');
  }

  addComponentToRow(component: Type<any>, row: HTMLElement, param: string) {
    let container = new HtmlContainer(row, this.appRef, this.componentFactoryResolver, this.injector);
    let componentRef = container.attach(component);
    componentRef.instance.param1 = param;

    this.containers.push(container);
  }

  ngOnDestroy() {
    this.containers.forEach(container => container.dispose());
  }
}

Plunker Example

See also

查看更多
登录 后发表回答