skip to Main Content

I have a full Angular 16 application, containing routing, multiple components and services and I need to convert it into a single Webcomponent, that can be loaded via the script tag in an external UI application/shell.

So far, all the tutorials and resources I have come across demonstrate transforming just a single component Angular app into a Webcomponent using Angular Elements, such as this example –

import { Injector, NgModule } from '@angular/core';
import  { createCustomElement } from '@angular/elements';
import {HttpClientModule} from "@angular/common/http";
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing. Module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home. Component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent, HomeComponent]
})
export class AppModule {

  constructor(private injector: Injector) {
    const el = createCustomElement(HomeComponent, {injector: this. Injector});
    customElements.define('element-home', el);
  }

  ngDoBootstrap() {}
 }

Here, I have a single component in the Angular app, called HomeComponent, from which I can create a Webcomponent titled <element-home>. This can be loaded, as an example, into an HTML page this way –

<body>
  <element-home></element-home>
  <script src="...." /> // bundled Angular javascript files
</body>

But, how do I transform a complete Angular app (with routing) into a Webcomponent? My angular component structure looks like this –

- AppComponent
  <router-outlet> 
  |
   - HomeComponent // for /home route
   - DetailComponent // for /detail route
   - DetailService

Additionally, any tutorials/resources/guides on this topic would be appreciated!

2

Answers


  1. So if you use Angular 16 you can utilize standalone API (see the stackblitz link at the bottom):

    (WARNING: the app should not contain any lazy loaded parts)

    When you build that app in the repo you will get three js files:

    1. runtime.js
    2. polyfills.js
    3. main.js

    You have to concat them into one file keeping the order above.
    This way you will get one js file with your new webcomponent.

    import { Component, OnInit, ViewEncapsulation, inject } from '@angular/core';
    import { provideRouter, Router, RouterOutlet } from '@angular/router';
    import { createCustomElement } from '@angular/elements';
    import { createApplication } from '@angular/platform-browser';
    import 'zone.js';
    
    @Component({
      selector: 'home-component',
      standalone: true,
      template: `
        <h2 class="my-class">Hello from {{ name }}!</h2>
      `,
      styles: `
        .my-class {
          color: green;
        }
      `,
      encapsulation: ViewEncapsulation.ShadowDom,
    })
    export class HomeComponent {
      name = 'HomeComponent';
    }
    
    @Component({
      selector: 'app-component',
      standalone: true,
      template: `
        <h1 class="my-class">Hello from {{ name }}!</h1>
        <router-outlet></router-outlet>
      `,
      styles: `
        .my-class {
          color: red;
        }
      `,
      encapsulation: ViewEncapsulation.ShadowDom,
      imports: [RouterOutlet],
    })
    export class AppComponent implements OnInit {
      name = 'AppComponent';
    
      private router = inject(Router);
    
      ngOnInit() {
        this.router.initialNavigation();
      }
    }
    
    async function webComponentApp() {
      const app = await createApplication({
        providers: [
          provideRouter([
            { path: '', component: HomeComponent, pathMatch: 'full' },
          ]),
        ],
      });
      const element = createCustomElement(AppComponent, {
        injector: app.injector,
      });
      customElements.define('my-app-webcomponent', element);
    }
    
    (async function () {
      await webComponentApp();
    })();
    

    Stackblitz example

    Login or Signup to reply.
  2. Complementary the NgDaddy answer, some advertisements:

    I think remember read in some where that the "scripts" should be inserted at the end of the "body" -after the tag of your app-.

      <body>
        <my-app>
          loadding...
        </my-app>
    
        <script src="...."></script>
        <script src="...."></script>
        <script src="...."></script>
      </body>
    

    To use with modules you should write

    export class AppModule {
      constructor(private injector:Injector) {}
      ngDoBootstrap() {
        customElements.define(
          'my-app',
          createCustomElement(AppComponent, {injector: this.injector})
        )
      }
     }
    

    You can use "concat" to join the three bundles.For this.

    1. Add the concat

      npm i concat
      
    2. Create a file concat.js in the way

      var concat = require('concat');
      concat(['./dist/runtime.js',
              './dist/polyfills.js',
              './dist/main.js'],'./dist/my-app.js');
      
    3. And in package.json, before the build write

      //add this line
      "build-element": "ng build --prod=true --localize --output-hashing=none &&
                         node ./concat.js",
      
      //before
      "build":.....
      

    When you use routing, it’s possible you need use HashLocationStrategy

    { provide: LocationStrategy, useClass: HashLocationStrategy }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search