DEV Community

Cover image for How to Dynamically Render HTML Tags in Angular 16.2+
Sidharth Mohanty for Builder.io

Posted on • Edited on

How to Dynamically Render HTML Tags in Angular 16.2+

At Builder.io, we use Mitosis to generate multi-framework SDKs, enabling us to maintain one codebase that outputs code for React, Preact, Solid, Vue, Angular, Svelte, and more.. Some frameworks leverage normal JSX syntax like React but not all frameworks use JSX right? In React, achieving dynamic HTML generation is straightforward--simply use state to update the tag directly. However, with Angular, it wasn't possile until the version 16.2, which introduced the ngComponentOutlet directive.

Dynamic HTML Generation in React

Here’s an example of dynamic HTML generation in React:

function App() {
  const [Tag, setTag] = useState("div");

  const updateTag = (e) => {
    setTag(e.target.value);
  };

  return (
    <>
      <select onChange={updateTag}>
        <option value="div">div</option>
        <option value="span">span</option>
        <option value="p">p</option>
        <option value="a">a</option>
        <option value="h1">h1</option>
      </select>
      <Tag>Inside {Tag}</Tag>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

dynamic-html-tags-generation-react

In this example, the Tag state updates based on the selected value, rendering the corresponding HTML tag dynamically. We wanted to replicate this functionality in Angular. Is it possible? Yes, with some modifications!

Dynamic Component Generation in Angular

In Angular, you can achieve dynamic component generation using the ngComponentOutlet directive. Here’s how:

Step 1: Define Dynamic Components

First, we create components for each tag we want to generate dynamically (you can automate this using a script):

import { Component, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'dynamic-div, DynamicDiv',
  template: `<div #v><ng-content></ng-content></div>`,
  standalone: true,
})
export class DynamicDiv {
  @ViewChild('v', { read: ElementRef }) v!: ElementRef;
}

@Component({
  selector: 'dynamic-h1, DynamicH1',
  template: `<h1 #v><ng-content></ng-content></h1>`,
  standalone: true,
})
export class DynamicH1 {
  @ViewChild('v', { read: ElementRef }) v!: ElementRef;
}

@Component({
  selector: 'dynamic-a, DynamicA',
  template: `<a #v href=""><ng-content></ng-content></a>`,
  standalone: true,
})
export class DynamicA {
  @ViewChild('v', { read: ElementRef }) v!: ElementRef;
}

@Component({
  selector: 'dynamic-button, DynamicButton',
  template: `<button #v><ng-content></ng-content></button>`,
  standalone: true,
})
export class DynamicButton {
  @ViewChild('v', { read: ElementRef }) v!: ElementRef;
}
Enter fullscreen mode Exit fullscreen mode

Why are there unused refs here you ask? You can extend this functionality to dynamically add attributes or action attributes to any of the elements. A more complete example can be found here in our Opensource SDKs repository.

Step 2: Create a Dynamic Renderer Component

Next, we create a DynamicRenderComponent that will use the ngComponentOutlet directive to render the selected component dynamically:

import {
  Component,
  Input,
  ViewChild,
  ViewContainerRef,
  TemplateRef,
  Renderer2,
  OnChanges
} from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'dynamic-renderer',
  template: `
    <ng-template #tagTemplate><ng-content></ng-content></ng-template>
    <ng-container
      *ngComponentOutlet="Element; content: myContent">
    </ng-container>
  `,
  standalone: true,
  imports: [CommonModule]
})
export class DynamicRenderComponent implements OnChanges {
  @Input() Tag: any = 'div';
  @ViewChild('tagTemplate', { static: true }) tagTemplate!: TemplateRef<any>;

  Element: any = DynamicDiv;
  myContent: any;

  constructor(private vcRef: ViewContainerRef) {}

  ngOnChanges() {
    switch (this.Tag) {
      case 'div':
        this.Element = DynamicDiv;
        break;
      case 'button':
        this.Element = DynamicButton;
        break;
      case 'a':
        this.Element = DynamicA;
        break;
      case 'h1':
        this.Element = DynamicH1;
        break;
      default:
        this.Element = DynamicDiv;
        break;
    }
    this.myContent = [
      this.vcRef.createEmbeddedView(this.tagTemplate).rootNodes,
    ];
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create the Main Component

Finally, we create the main component that includes a dropdown to select the desired tag and renders the dynamic component accordingly:

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { DynamicRenderComponent } from './dynamic-render.component';

@Component({
  selector: 'app-root',
  template: `
    <select (change)="onChange($event)">
      <option value="div">div</option>
      <option value="button">button</option>
      <option value="span">span</option>
      <option value="p">p</option>
      <option value="a">a</option>
      <option value="h1">h1</option>
    </select>
    <dynamic-renderer [Tag]="Tag">Inside {{ Tag }}</dynamic-renderer>
  `,
  standalone: true,
  imports: [DynamicRenderComponent]
})
export class PlaygroundComponent {
  Tag = 'div';

  onChange(event: any) {
    this.Tag = event.target.value;
  }
}

bootstrapApplication(PlaygroundComponent);
Enter fullscreen mode Exit fullscreen mode

dynamic-html-tags-generation-angular

In this example, the PlaygroundComponent handles the tag selection, and the DynamicRenderComponent dynamically renders the selected tag. This approach ensures that we can generate dynamic HTML elements in Angular, similar to how it’s done in React.

Try it out yourself using this GitHub gist. I hope you found this post insightful. While I'm not an Angular expert, we wanted to solve this problem and share our approach. If you know of a better way to handle this specific scenario in Angular, please share your insights in the comments. Your feedback and suggestions are always welcome!

Top comments (0)