2

I have a use-case, where I should render an external angular component or web-component in an angular application.

I have created a sample application (stackblitz) to reproduce the scenario and below is the visual expecation.

Dynamic rendering expectation

In the sample code, to compile a angular component I use

<div style="margin:20px; display: flex; max-height: 500px;">
  <div style="max-width:250px; overflow-y: scroll;">
    <button (click)="onInternalControl()">Internal Component</button>

    <ng-template #internalTemplate>this is internal template</ng-template>
  </div>

  <div style="width:250px; height: 150px; overflow-y: scroll;">
    <button (click)="onExternalControl()">External Component</button>

    <ng-template #externalTemplate>this is external template</ng-template>
  </div>
</div>

The below is the component code

export class AppComponent implements OnInit {
  @ViewChild('internalTemplate', { read: ViewContainerRef })
  internalTemplate: any;

  @ViewChild('externalTemplate', { read: ViewContainerRef })
  externalTemplate: any;

  constructor() {}

  ngOnInit() {}
  onInternalControl() {
    this.internalTemplate.clear();
    this.internalTemplate.createComponent(LabelComponent);
  }

  onExternalControl() {
    this.externalTemplate.clear();
    // how to dynamically register and load web-component in the ng-template (externalTemplate)
    // e.g., assets/external-label.js
  }
}

The below code is the web component which is exepected to be render on click of EXTERNAL_COMPONENT button.

class ExternalLabel extends HTMLElement {
  // The browser calls this method when the element is
  // added to the DOM.
  connectedCallback() {
    let $p = document.createElement('p');
    $p.style.backgroundColor = 'red';
    $p.style.width = '250px';
    $p.style.height = '250px';
    $p.innerHTML = 'External Label Control';
    this._shadowRoot.appendChild($p);
  }
}

// Register the ExternalLabel component using the tag name <ext-label>.
customElements.define('ext-label', ExternalLabel);

How can I dynamically register and load the external label web component?

1
  • EDIT: I overlooked a crucial requirement in my post. I need to register web components dynamically and render them within the Angular application. Commented Jul 23, 2024 at 6:57

1 Answer 1

0

I think there is some problem in your web component, here is my web component that works fine.

customElements.define(
  'fancy-tabs',
  class extends HTMLElement {
    constructor() {
      super(); // always call super() first in the constructor.

      // Attach a shadow root to <fancy-tabs>.
      const shadowRoot = this.attachShadow({ mode: 'open' });
      shadowRoot.innerHTML = `
      <style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
      <div id="tabs">...</div>
      <div id="panels">...</div>
  `;
    }
  }
);

When it comes to web components, make sure you import the script in the scripts array of angular.json.

      ...
      "options": {
        "assets": ["src/app/assets"],
        "index": "src/index.html",
        "browser": "src/main.ts",
        "outputPath": "dist/demo",
        "scripts": ["src/app/assets/externalLabel.js"],
        "styles": ["src/global_styles.css"],
        "tsConfig": "tsconfig.app.json"
      }
      ...

Then the web-component, is best to exist in a wrapper component, because we can use CUSTOM_ELEMENTS_SCHEMA to prevent the error that the selector is unknown. This can also be done at the module level, but why I recommend this is because the schema will block some errors, so your angular application will not show errors properly.

import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
  selector: 'app-external',
  standalone: true,
  imports: [],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `test<fancy-tabs></fancy-tabs>`,
})
export class ExternalComponent {}

Finally we can render this wrapper component as the way we do our normal component.

import { Component, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { LabelComponent } from './label.component';
import { ExternalComponent } from '../external/external.component';

@Component({
  selector: 'my-app',
  standalone: true,
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  @ViewChild('internalTemplate', { read: ViewContainerRef })
  internalTemplate: any;

  @ViewChild('externalTemplate', { read: ViewContainerRef })
  externalTemplate: any;

  constructor() {}

  ngOnInit() {}
  onInternalControl() {
    this.internalTemplate.clear();
    this.internalTemplate.createComponent(LabelComponent);
  }

  onExternalControl() {
    this.externalTemplate.clear();
    this.externalTemplate.createComponent(ExternalComponent);
    // how to dynamically register and load web-component in the ng-template (externalTemplate)
    // e.g., assets/external-label.js
  }
}

Stackblitz Demo

Sign up to request clarification or add additional context in comments.

2 Comments

I missed out the key requirement in my question. I suppose to register the web-components dynamically and render the same in the angular application. So, I cannot register the scripts in angular.json file. Is there a way to dynamically register also, attach the web-component dynamically in the app?
@jayalakshmi.k Try using script tag. article reference

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.