My starting point is some kind of HTML snippet, which is loaded at runtime from my backend, with placeholder tags:
<table>
<tbody>
<tr>
<td>
<span>Col1</span>
</td>
<td>
<span class="placeholderClass" data-placeholderdata="xy"></span>
</td>
</tr>
</tbody>
</table>
I want to display the HTML snippet but replace all <span class="placeholderClass"/>
elements with my own angular component.
So far I read about the Dynamic component loader from angular, but as far as I understood this only allows my to load an dynamic component on a fixed position, and not a fixed component on a dynamic position.
Also I tried following post: Angular 2 Dynamically insert a component into a specific DOM node without using ViewContainerRef
Section Component is my main component where I store the HTML and Value Component the component I want to replace.
Section Component:
HTML
<div [innerHTML]=sectionSnippet></div>
TS
@Component({
selector: 'section-view',
templateUrl: './section.component.html',
styleUrls: ['./section.component.sass'],
encapsulation: ViewEncapsulation.ShadowDom
})
export class SectionComponent implements OnInit {
@Input() sectionSnippetInput = '';
constructor(private sanitizer: DomSanitizer,
private resolver: ComponentFactoryResolver,
private injector: Injector,
private app: ApplicationRef) {
}
ngOnInit(): void {
this.replaceTags();
}
private replaceTags() {
let temp:HTMLDivElement = document.createElement('div');
temp.innerHTML = this.sectionSnippetInput;
let placeholders: HTMLCollectionOf<Element> = temp.getElementsByClassName('placeholderClass')
for (let i = 0; i < placeholders.length; i++) {
//dynamically create angular comp
let factory = this.resolver.resolveComponentFactory(ValueComponent);
//create instance with the value placeholder as parent
const ref = factory.create(this.injector, [], placeholders[i]);
//manually init comp
ref.instance.initElem = placeholders[i];
ref.instance.initComp();
//trigger re-rendering
ref.changeDetectorRef.detectChanges();
//attach newly created component to our angular context
this.app.attachView(ref.hostView);
}
this.sectionSnippetInput = temp.innerHTML;
}
get sectionSnippet() {
return this.sanitizer.bypassSecurityTrustHtml(this.sectionSnippetInput);
}
}
Value Component:
HTML
<span (click)="testCallback()">{{getFormatNumber()}}</span>
TS
@Component({
selector: 'value',
templateUrl: './value.component.html',
styleUrls: ['./value.component.sass']
})
export class ValueComponent implements AfterViewInit {
public param1:string|null = '';
public initElem: Element | null | undefined;
public testCallback(){
alert("Test")
}
ngAfterViewInit(): void{
this.initComp();
}
public initComp() {
// @ts-ignore
this.param1 = this.initElem.getAttribute("data-placeholderdata");
}
getFormatNumber(){
//some formatting code
return this.param1;
}
}
Which seems to work, at least for the initial rendering, but angular bindings like (click) does not work. My guess is that the newly created component is not correctly bound to the angular context, therefore it does not register the click events.
(Using angular V13)
As I am new to angular, can someone give me advice how to proceed. Is using the ComponentFactory a good idea of should I try something different?