angularelementref

Whats the Angular way of cloning/moving an element into the body with all event listeners?


For example i have something like this:

FancyComponentWantsToShowAnOverlay.ts

  ...
  ClickToDoSomething(): void {
    // doing something
  }

  MoveOrCloneToBody(): void {
    // do the magic
  }
  ...

FancyComponentWantsToShowAnOverlay.html ...

* displaying fancy stuff *

<ng-template #MoveOrCloneMeToBodyPlease>
  * seriously beautiful stuff will be displayed here *
</ng-template>

* displaying more fancy stuff *
<a (click)="MoveOrCloneToBody()">do the magic</a>

MoveOrCloneMeToBodyPlease should be appended to body (removing also would be nice later).

Thanks!


Solution

  • Dynamically Add/Remove Html Elements

    *ngFor

    You can create a component of the 'something beautiful' and use the ngFor directive to loop over an array which you can add or remove elements to/from. All the eventlisteners would be in the components that are added.

    Template:

    <app-some *ngFor="let color of colors"></app-some>
    

    Script:

    export class AppComponent  {
      colors = [{}]
    
      addColor() {
        this.colors.push({})
      }
    
      removeColor() {
        this.colors.pop()
      }
    }
    

    Here is an example in a Stackblitz

    Renderer2

    You can also use renderer2 to manipulate the DOM, where you can bind to the element you want to append children to using @ViewChild annotation. You can add listeners to elements using renderer2, so this would also fit your purpose.

    Template:

    <div #container></div>
    

    Script:

    export class AppComponent  {
    
      @ViewChild("container")
      el: ElementRef
    
      children = []
    
      constructor(private renderer: Renderer2) {}
    
      createSOmethingBeautiful() {
        let div = this.renderer.createElement('div')
        let text = this.renderer.createText('Beautiful stuff');
        this.renderer.appendChild(div, text);
        this.children.push(div)
        return div
      }
    
      add() {
        this.renderer.appendChild(this.el.nativeElement, this.createSOmethingBeautiful())
      }
    
      remove() {
        this.renderer.removeChild(this.el.nativeElement, this.children.pop())
      }
    }
    

    Here is another example in a Stackblitz.

    InnerHTML

    You can also bind to the innerHtml from any html element. But, this would not contain any eventlisteners so wouldn't solve your problem.

    Template:

    <div [innerHTML]="html"></div>
    

    Script:

    export class AppComponent {
      private _html = [''];
    
      get html () {
        return this._html.join('')
      }
    
      add() {
        this._html.push('<p>Something pretty</p>')
      }
    
      remove() {
        this._html.pop()
      }
    }
    

    And yet another example in a Stackblitz.