How to Dynamically Render HTML Tags in Angular 16.2+
Tags: webdev, angular, javascript, tutorial
Published: 2024-06-22 10:17:16

At Builder.io, we use [Mitosis](https://mitosis.builder.io/) 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:

```jsx
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>
    </>
  );
}
```

![dynamic-html-tags-generation-react](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c6hj4izjbjd1ud18yuv5.gif)

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):

```tsx
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;
}
```
> 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](https://github.com/BuilderIO/builder/blob/main/packages/sdks/overrides/angular/src/components/dynamic-renderer/dynamic-renderer.ts).

### Step 2: Create a Dynamic Renderer Component

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

```tsx
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,
    ];
  }
}
```

### 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:

```tsx
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);
```

![dynamic-html-tags-generation-angular](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rr9x6cmbuloj817xymzl.gif)

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](https://gist.github.com/sidmohanty11/74a2fb93ce2b512f49bb64020520222c). 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!