Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
955 views
in Technique[技术] by (71.8m points)

typescript - Angular 2: Inserting capture element dynamically when creating components (dynamically)

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.
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...