Search code examples
angulartypescriptweb-componentangular-elements

Strange behavior using Angular elements with application builder v19


I created a demo app using the @angular/elements package (v19) to build an mfe-1 app as a web component, which is then displayed inside another app called shell. Both apps are set up with the Angular application builder and are housed within an Nx monorepo to streamline the workflow.

Here’s an overview of my setup:

1- App mfe-1:

  • Displays a simple text.
  • Built using standalone component architecture.
  • The bootstrap process was modified to convert the app into a web component.

For reference, the main.ts file looks like this:

import { createCustomElement } from '@angular/elements';
import {
  createApplication
} from '@angular/platform-browser';

import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

(async () => {
  const appRef = await createApplication(appConfig);
  const appComponent = createCustomElement(AppComponent, {
    injector: appRef.injector,
  });

  customElements.define('nds-mfe-1', appComponent);
})();

everything else is standard to angular apps in the new standalone style.

i adjusted the build process to not hashing the build output, so that i can easily integrate the main.js file in another app.

  1. APP two is a shell app, that should be used to display nds-mfe-1 element. Shell also doesnt have many content and simply have a placeholder from the generated app.

i wrote a simple service to integrate the main.js script as module into the page, so that i can render my element.

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

@Injectable({
  providedIn: 'root',
})
export class MicroFrontendLoaderService {
  private loaded = false;

  loadScript(url: string): Promise<void> {
    return new Promise((resolve, reject) => {
      if (this.loaded) {
        resolve();
        return;
      }

      const script = document.createElement('script');
      script.src = url;
      script.type = 'module';
      script.onload = () => {
        this.loaded = true;
        resolve();
      };
      script.onerror = () => {
        reject(new Error(`Failed to load script: ${url}`));
      };
      document.body.appendChild(script);
    });
  }
}

As soon as i call the script inside my component, the component is rendered twice. This make my main.js appear twice in the DOM as script tag. My web components does not appear on the page.

Here is the full reproduction of my issue on github.. Just run npm install and then nx serve shell to start the setup.

Can someone explain why i get this behavior?

enter image description here

constructor runs twice as soon as i try to include the web component script


Solution

  • enter image description here

    I've placed your script in the public folder, which is a replacement for the assets folder in newer Angular versions. Also, I renamed the resulting main.js file to custom-element.js to avoid name collision.