How to dynamically add a cloned node in angular2 (equivalent to cloneNode)

GWorking picture GWorking · Jan 29, 2017 · Viewed 9.6k times · Source

In Angular2, I need to duplicate a node rather than moving it in some cases. That node has angular2 properties so cloneNode doesn't work. How can I do it?

*what doesn't work

    let el = <HTMLElement>document.getElementById(divId);
    if ((<HTMLElement>el.parentNode).id == 'itsMe')
        el = <HTMLElement>el.cloneNode(true);
    document.getElementById(anotherId).appendChild(el);

*what would work, from Angular2: Cloning component / HTML element and it's functionality

@Component({
  selector: 'my-app',
  template: `
    <template #temp>
        <h1 [ngStyle]="{background: 'green'}">Test</h1>
        <p *ngIf="bla">Im not visible</p>   
    </template>
    <template [ngTemplateOutlet]="temp"></template>
    <template [ngTemplateOutlet]="temp"></template>
    `
})

export class AppComponent {
    bla: boolean = false;
    @ContentChild('temp') testEl: any;
} 

But how to add a template dynamically?

Answer

AngularChef picture AngularChef · Jan 29, 2017

Let's use the following markup for illustration:

<p>Paragraph One</p>
<p>Paragraph Two</p>   <!-- Let's try to clone this guy -->
<p>Paragraph Three</p>

Option 1 - Manually wrap the element to clone inside a <template> tag

This is basically what you did, only instead of printing out the template with ngTemplateOutlet, grab a reference to it in your component's class and insert it imperatively with createEmbeddedView().

@Component({
    selector: 'my-app',
    template: `
      <p>Paragraph One</p>
      <template #clone>
        <p>Paragraph Two</p>
      </template>
      <p>Paragraph Three</p>

      <button (click)="cloneTemplate()">Clone Template</button>

      <div #container></div>
    `
})
export class AppComponent{
    // What to clone
    @ViewChild('clone') template;

    // Where to insert the cloned content
    @ViewChild('container', {read:ViewContainerRef}) container;

    constructor(private resolver:ComponentFactoryResolver){}

    cloneTemplate(){
        this.container.createEmbeddedView(this.template);
    }
}

In this example, I'm inserting the "clone" at a specific location in the markup (<div #container></div>), but you could also append it at the bottom of the current component's template.

Also note that the original <p>Paragraph Two</p> is no longer visible.

Option 2 - Use a structural directive

If you want to clone an element at its current location, ending up with:

<p>Paragraph One</p>
<p>Paragraph Two</p>   <!-- Original paragraph -->
<p>Paragraph Two</p>   <!-- Cloned paragraph   -->
<p>Paragraph Three</p>

Then you could create a structural directive *clone and apply it to the paragraph to clone, like this:

<p>Paragraph One</p>
<p *clone>Paragraph Two</p>
<p>Paragraph Three</p>

Interestingly, what a structural directive does is wrap the element it is applied to inside a <template> tag. Pretty similar to what we did in option 1, only in that case we have NO CONTROL over the location where the clones are printed out (they will appear where the original paragraph was).

This would essentially replicate *ngFor's behavior so it's probably not very useful. Also, it seems from your comment to yurzui that it's not what you want.