How can I dynamically load Angular components in subsections of a document being loaded as innerHtml?

Viewed 142

I have an Angular component, "app-xml-content".

Within app-xml-content, I load some XML from an external source. I want to then highlight certain sections of that XML, and add a tooltip with some helpful information.

I have achieved this as follows:

<div id="xml-content" [innerHTML]="xmlString | customHighlight: criteria"></div>

My customHighlight pipeline will search the XML, and currently just does a RegEx replace:

const highlighter = (match) => `<mark class="highlight${diff.intensity}">${match}</mark>`;
if (diff.intensity) {
    modifiedContent = modifiedContent.replace(new RegExp(pattern, 'gi'), highlighter);
}

But I would prefer for the highlighter to replace matches with a custom Angular component, rather than an HTML <mark />. E.g.

const highlighter = (match) => `<app-highlight intensity="${diff.intensity}" content="${match}"></app-highlight>`;
if (diff.intensity) {
    modifiedContent = modifiedContent.replace(new RegExp(pattern, 'gi'), highlighter);
}

When I do this, the highlighted sections are simply not rendered.

I can see that the constructor for <app-highlight /> is not called.

I've investigated, and found ComponentFactoryResolver, but I don't really understand how to use it to render only the bits of the XML being replaced. Can I use the resolver in the pipe to resolve it to the AppHighlightComponent?

If someone can show me a pattern I can use that will help me in this, I'd really appreciate it.

Thanks.

1 Answers

One possible solution would be to create a web component from your angular component by using angular element. For this in your AppModule you need to add AppHighlight as an entry component, declare it as a custom element and the ngDoBootsrap method.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, DoBootstrap, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { AppHightlight } from 'path/to/AppHighlight';

@NgModule({
  declarations: [
    AppHighlight,
  ],
  imports: [
    BrowserModule,
  ],
  entryComponents: [AppHighlight],
})
export class AppModule implements DoBootstrap {

  constructor(private injector: Injector) {
    const webComponent = createCustomElement(AppHighlight, {injector});
    customElements.define('custom-app-highlight', webComponent);
  }

  ngDoBootstrap() {}
}

Then whenever custom-app-highlight is injected in the DOM, your browser will interpret it as your angular component.

<div>
    <custom-app-highlight intensity="intensity string" content="content string" />
</div>

EDIT 1: Note that inputs for angular elements can't contain upper case and they should be strings EDIT 2: removed the link to angular elements to give an implementation example with code

Related