How do you create web components with customizable templates using Angular Elements?

695 Views Asked by At

I would like to create a library of web components using Angular Elements that have default templates but allow developers to override the output.

For example, consider a search-results component. I might have a default template that looks like this:

<h1>Search results for: {{query}}</h1>

But a developer might want to change the output to this (as an arbitrary example -- it needs to be flexible):

<h1>You searched for <strong>{{query}}</strong></h1>

My first thought was to simply use ng-content like this:

<search-results>
    <h1>You searched for <strong>{{query}}</strong></h1>
</search-results>

However, this does not work and will literally output {{query}}.

I then discovered that I could add a TemplateRef input parameter on my component and pass in an ng-template element which would parse expressions. This works well within an Angular application, but I'm not sure how to do this outside of an Angular context using the compiled web components that Angular Elements generates.

Can anyone explain how to do this?

3

There are 3 best solutions below

0
On

I see two solutions.

Solution 1: HTML template

Define a HTML template and pass its id to the Angular component. There you clone that node (see example in link) and add it to the DOM.

Placeholders ({{query}}) do not work "out of the box" in that template. You could replace them manually or just update the template and watch for changes in the Angular component. (Mutation Observer)

I'm working on this idea right now... I'll post an update here once my code is on GitHub so you can have a look at it.

Solution 2: JS templates

Another idea is to work with JS templates. (EJS for example)

You define a template string that you pass to the Angular component. There you render it with the given data object.

0
On

You can create a function that parse the .....{{variable}}... to ...value...

  replaceText(content: string) {
    const match = content.match(/(\{\{\w+\}\})/g)
    match?.forEach(x => {
      const variable = x.slice(2).slice(0, -2) || "fool"
      const value = (this as any)[variable] || ""
      content = content.replace(x, value)
    })
    return content
  }

Then, you store the "ng-content" innerHTML in ngAfterviewInit. When you need, you call to this function.

Imagine some like -see that the "ng-content" is under a div "inner" with display:none

@Component({
  template: `
    <div class="alert alert-{{ type }} alert-dismissible" *ngIf="show">
      
      <div [innerHTML]="newContent"></div>
      <button type="button" class="close">
        <span (click)="show = false; closed.emit()">&times;</span>
      </button>
    </div>
    <div #inner class="hidden">
         <ng-content></ng-content>
      </div>

  `,
  styles:[`
  .hidden{
    display:none
  }
  `]
})

In ngAfterVieInit

  @ViewChild('inner', { static: false }) inner!: ElementRef;
  content:any;

  ngAfterViewInit(): void {
    this.content = this.inner.nativeElement.innerHTML;
  }

And when you need

this.newContent = this.satinizer.bypassSecurityTrustHtml(
  this.replaceText(this.content || '')
);

See a simple stackblitz

0
On

Use bypassSecurityTrustHtml method of DomSanitizer, provided by an angular; and bind it with html <div [innerHtml]="getSearchText()"></div>.

public getSearchText() {
    return this.domSanitizer.bypassSecurityTrustHtml(`You searched for <b>${this.searchText}</b>`);
}

For more visit the angular documentation https://angular.io/api/platform-browser/DomSanitizer