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>
</>
);
}
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;
}
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,
];
}
}
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);
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!