3

I built an Angular component that displays an array of markers on a map. When one of these markers gets clicked a popup will be displayed. The content of this popup can be defined by the user of my component.

I solved this by accepting an ng-template as @Input parameter.

<my-map
  [markers]="markers"
  [popupTemplate]="popupTemplate">
</my-map>

<ng-template #popupTemplate let-marker>
  <h1>{{marker.title}}</h1>
  <div>{{marker.subtitle}}</div>
</ng-template>

This works fine! But now I am publishing this component as a Web Component using Angular Elements. How do I solve the templating part for the Web Component?

Sebi
  • 2,534
  • 2
  • 26
  • 28

2 Answers2

0

I can not see the problem. well, generally you should define a "default template"

In you component, I suppose you has a *ngFor

<div *ngFor="let marker of markers">

    <ng-container *ngTemplateOutlet="template?template:defaultTemplate;
                        context:{$implicit:marker}" >
    </ng-container>
</div>

<ng-template #defaultTemplate let-marker>
  <h1>{{marker.title}}</h1>
  <div>{{marker.subtitle}}</div>
</ng-template>
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • 1
    The Web Component will be used in a non-Angular application. Which means I cannot simply define a ng-template and pass it to the component. – Sebi Apr 05 '21 at 06:53
  • @Sebi did you ever find a solution to this? I'm evaluating using Angular Elements for a project and ended up with the exact same question without any luck finding an answer. – JKasper11 Apr 04 '22 at 00:21
  • Either of you ( @JKasper11 ) find a solution? – Charly Jul 26 '22 at 14:43
  • @Charly Negative -- I found very few resources for Angular Elements and couldn't get any responses from the Discord so I ended up not using it. – JKasper11 Jul 26 '22 at 18:31
  • @JKasper11 This saddens me. I've got a hacky solution I'll post to your question hopefully today as it fits over there more (https://stackoverflow.com/questions/71819490/how-do-you-create-web-components-with-customizable-templates-using-angular-eleme) – Charly Jul 27 '22 at 17:40
0

A possible solution to this may be to accept a template string and the component would need to know how to deal with it.

A function for handling template strings:

export const template = (str, vars): string => new Function(`return \`${ str.replace(/\${/g, '${this.') }\`;`).call(vars);

Essentially builds a function on the fly to treat a given string as if it were a `template ${string}`

Usage:

let myTemplate = 'Just a ${field}';
let vars       = { field: 'test' };
let str        = template(myTemplate, vars); // "Just a test"

Option 1:

The Angular component could accept a template string and perform this template against it, then feed it to the DOM sanitizer for safe HTML to be used as an [innerHTML]

Annoying, but an option.

Option 2:

Could accept it as content

<my-map [markers]="markers">
  <template>
    <h1>${marker.title}</h1>
    <div>${marker.subtitle}</div>
  </template>
</my-map>

Angular Elements doesn't support ngContent or ngContentChildren (doesn't convert them to <slot> or anything either), so this option would require fancy footwork on behalf of the Angular Component.

The component's template needs to spit out the given content, something like

<ng-content></ng-content>

Somewhere in it's template so it'll render the given content.

Then in the component, grab the DOM contents to be templated. (probably after ngOnInit)

this.hackyTemplateHtml = this.elRef.nativeElement.querySelector('template').innerHtml;

If requiring multiple templates, could have the implementation send them with a unique attribute or class <template class='marker-template'>

For the template loop, would need to send each marker to be templated:

<div *ngFor="let marker of markers; trackBy: trackByTitle">
  <ng-container *ngTemplateOutlet="hackyTemplateHtml ? hackyTemplate : defaultTemplate;
                    context:{$implicit:marker}" >
  </ng-container>
</div>

<ng-template #defaultTemplate let-marker>
  <h1>{{marker.title}}</h1>
  <div>{{marker.subtitle}}</div>
</ng-template>

<ng-template #hackyTemplate let-marker>
  <div [outerHTML]="templateHacky(marker)"></div>
</ng-template>

The ngFor should probably implement a trackBy so it's not re-templating constantly. and the [outerHTML] is so it replaces the <div>, so it's not wrappered.

Back in the component:

trackByTitle(idx, marker) {
  return marker.title;
}

templateHacky(marker: Marker) {
  let templated = template(this.hackyTemplateHtml, { marker });
  return this.sanitizer.bypassSecurityTrustHtml(templated);
}

Also definitely hacky. Best I could think of as plain HTML/JS implementation. I'm working on a solution for react/vue to be able to send the equivalent of an Angular TemplateRef to Angular Elements to be treated as a normal template. Needs further thinking of how to do it frameworkless though.

Charly
  • 881
  • 10
  • 19