angularangular-elements

Angular Element - body strangely getting appended at the end


This is strange. Whenever I render my custom element, the body (innerHTML) of the element gets appended at the end.

This is my homepage - see 'childbody' is in the body of the s-child element:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<s-child>childbody</s-child>'
})
export class AppComponent {
  title = 'test-app';
}

This is my s-child element - Note that I'm not rendering <ng-content> anywhere:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-child',
  template: '<p>child works!</p>'
})
export class ChildComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }
}

This is the output when I run this app - Note that 'childbody' is appended at the end, despite it not being 'asked to render anywhere' (i.e the weird part) -

enter image description here

This is my app-module where I declare the custom element - this is just FYI / additional context:

import { BrowserModule } from '@angular/platform-browser';
import { CUSTOM_ELEMENTS_SCHEMA, Injector, NgModule, NO_ERRORS_SCHEMA } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ChildComponent } from './test/child/child.component';
import {createCustomElement} from '@angular/elements';

@NgModule({
  declarations: [
    AppComponent,
    ChildComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  entryComponents: [ChildComponent],
  schemas: [
    CUSTOM_ELEMENTS_SCHEMA
  ]
})
export class AppModule {
  constructor(injector: Injector) {

    const child = createCustomElement(ChildComponent, {injector: injector});
    customElements.define('s-child', child);
  }
}

I wanted to provide a Stackblitz, but that's behaving weirdly with custom elements, apologies for that. I'm using Angular 10.2.4.


Solution

  • Ok, after 6.5 hours of head-banging, I figured out a solution -

    1. When the element is used directly within a component - The selector of the corresponding element should be used (not the element tag).

    2. When an element is dynamically inserted, the tag for custom element can be used, possibly with a sanitizer, e.g. -

      constructor(private sanitizer: DomSanitizer) {
      }
    
      ngOnInit() {
        this.sanitizedData = this.sanitizer.bypassSecurityTrustHtml('<s-child>adsf</s-child>')
      }