Search code examples
javascripthtmlangulardom

How to insert a dynamic component inside a element in Angular


Imagine we have a html structure as follows

<div [innerHTML]="contentFromAPI | safeHTML"></div>

Now the content from the api would be a string consists of more HTML elements. Making the structure something like this:

<div>
 <p></p>
 <p></p>
 <p></p>
 <p></p>
 <p></p>
 <p></p>
 <p></p>
 <p></p>
 <p></p>
 <p></p>
</div>

I want to create a component dynamically and insert it at the middle of the child nodes. In this case at the 5th place.

For this I'm doing something like this:

html file:

<div [innerHTML]="contentFromAPI | safeHTML" #articleContent></div>

ts file:

@ViewChild('articleContent', { read: ViewContainerRef })
  private articleContent!: ViewContainerRef;

ngOnChanges(){
 if(ads){
   this.addElementToTemplate();
 }
}

private addElementToTemplate() {
setTimeout(() => {
      const childrenLength =
        this.articleContent.element.nativeElement.children.length;
        this.articleContent.createComponent(DynamicElement,{index: Math.ceil(childrenLength/2)});
    }, 3000);
}

but this adds the component as a sibling to content element. Something like this:

<div>
     <p></p>
     <p></p>
     <p></p>
     <p></p>
     <p></p>
     <p></p>
     <p></p>
     <p></p>
     <p></p>
     <p></p>
</div>
<dynamic-element></dynamic-element>

Solution

  • It is possible to insert an angular component inside innerHTML but we should note that elements inserted this way are outside angular context and change detection will not work, so it will behave like a normal HTML element. Its better you looks for alternative approaches and not using innerHTML, Please find below example highlighting my point that change detection does not work inside innerHTML

    full code

    import {
      ApplicationRef,
      Component,
      Renderer2,
      ViewChild,
      ViewContainerRef,
      createComponent,
    } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    import { SafeHtmlPipe } from './safe-html.pipe';
    import { CommonModule } from '@angular/common';
    import { DynamicComponent } from './app/dynamic/dynamic.component';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [SafeHtmlPipe, CommonModule, DynamicComponent],
      template: `
        <div [innerHTML]="contentFromAPI | safeHtml" #articleContent></div>
      `,
    })
    export class App {
      @ViewChild('articleContent', { read: ViewContainerRef })
      private articleContent!: ViewContainerRef;
      name = 'Angular';
      contentFromAPI = `
      <p>1</p>
      <p>2</p>
      <p>3</p>
      <p>4</p>
      <p>5. insert here!</p>
      <p>6</p>
      <p>7</p>
      <p>8</p>
      <p>9</p>
      <p>10</p>
      `;
    
      constructor(
        private renderer2: Renderer2,
        private applicationRef: ApplicationRef
      ) {}
    
      ngAfterViewInit() {
        this.addElementToTemplate();
      }
    
      private addElementToTemplate() {
        const environmentInjector = this.applicationRef.injector;
        const componentRef = createComponent(DynamicComponent, {
          environmentInjector,
        });
        const insertElement = this.articleContent.element.nativeElement;
        console.log(insertElement);
        this.renderer2.appendChild(
          insertElement.children[4],
          componentRef.location.nativeElement
        );
        this.articleContent.createComponent(DynamicComponent, {
          index: 0,
        });
        componentRef.hostView.detectChanges();
      }
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo