chevron-left chevron-right

How to convert Angular component into reusable Web Component?

In this article I'm not going to tell you much about basics of Angular component creation. I'm going to focus on converting an existing one into a Web Component. Such an approach can be really helpful while migrating/updating your codebase to a new solutions.

Before we get down to the clue of this article I would like to inform you, that at the very beginning this post was a small part of a bigger one. When I started writing it, I realized that there will be too much information in one huge piece of text. After deeper text analysis I decided to split the bigger one into smaller pieces, so it will be easier to adopt the knowledge in any kind of frontend projects.

Sample Angular component

Firstly, let's introduce a sample Angular component we're going to convert into a Web Component. The component itself is very simple. It consists of:

  • a headline,
  • an image,
  • a button that dispatches an event

It's nothing very sophisticated. Let's see the code then:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/app/AngularElement/AngularElement.component.ts
import { Component, OnInit, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core';
 
@Component({
  selector: 'app-angular-element',
  template: `
    <img src="/images/ng.svg" alt="Angular Logo" class="logo" />
    <p>Hi <strong>{{name}}</strong>! It's your Angular component here!</p>
    <button type="button" class="btn btn-secondary" (click)="emitOnHello.next()">Click me to say hello!</button>
  `,
  styleUrls: ['./AngularElement.component.scss'],
  encapsulation: ViewEncapsulation.None // <- this allows CSS to bleed to this component from parent app
})
export class AngularElementComponent implements OnInit {
  @Input() name: string;
  @Output() emitOnHello: EventEmitter<string> = new EventEmitter();
  constructor() {}
  ngOnInit() {}
}

The most important thing to be aware of in this component is the emitOnHello callback that emits a special event that will be listened by other components outside of the Angular component.

Converting an Angular component into a Web Component

In this section we are going to convert the AngularElementComponent into a Web Component. Let's see how it's done:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { AppComponent } from './app.component';
import { 
    AngularElementComponent 
} from './AngularElement/AngularElement.component.component';
 
@NgModule({
  declarations: [
    AppComponent,
    AngularElementComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [],
  entryComponents: [
    AppComponent,
    AngularElementComponent
  ]
})
export class AppModule {
  constructor(private injector: Injector) { }
 
  ngDoBootstrap(): void {
    const { injector } = this;
    // create custom elements from angular components
    const ngCustomElement = createCustomElement(AngularElementComponent, { injector });
 
    // define in browser registry
    customElements.define('blog-angular', ngCustomElement);
  }
}

From all the lines of code above the most important parts are related to the usage of import { createCustomElement } from '@angular/elements';. There we've imported a function that allows to create a new custom element (Web Component) from any Angular components. Just like here:

1
2
3
4
5
6
7
8
ngDoBootstrap(): void {
    const { injector } = this;
    // create custom elements from angular components
    const ngCustomElement = createCustomElement(AngularElementComponent, { injector });
 
    // define in browser registry
    customElements.define('blog-angular', ngCustomElement);
}

In the ngDoBootstrap hook we're bootstrapping the Angular module manually. Thanks to that approach we're able to expose a new custom element outside of Angular scope. We've created a new custom element by invoking createCustomElement function with proper params and then we've registered a new custom element in the browser global scope by running customElements.define('blog-angular', ngCustomElement);. By doing this we'll be able to use such Angular Web Component in any non-Angular project just by inserting HTML tag.

Proper usage of Angular Web Component outside of Angular project

So far, we've exported the Angular component into a Web Component. The next step is to use it in a non-Angular project. Im my sample project, the app was stored inside the src/main.ts file, by a proper configured BabelJS and Webpack it gets converted into dist/main.js file. I'm going to link this file with my project. To make it simpler I'll just copy it and rename it as angular.element.js and link it inside HTML with the script tag:

The last thing is to start using exposed HTML tag and provide some data within attributes and properties.

1
2
3
4
<body>
  <h1>Custom Angular Web Component</h1>
  <blog-angular id="angular" />
</body>

In order to enable the communication between Angular Web Component and the rest of page we need to pass an event name into the Angular component and attach an event listener to the HTML tag with Web Component. See how I did it:

1
2
3
4
5
const element = document.getElementById('angular');
const name = 'Piotr';
 
element.setAttribute('name', name);
element.addEventListener('emitOnHello', () => console.log('Angular'));

The additional JavaScript code required to handle custom Web Component is fairly simple. We're setting the value of the name attribute with JavaScript code as it can be dynamically changed based on interaction with other elements on a page.

In order to listen to events emitted from the Angular component we had to implement an event listener that listens to emitOnHello event. In the sample above it runs the console.log function, but in your case you can do anything you want in your implementation of emitOnHello event callback.

Summary

Converting the Angular components into reusable Web Components might be confusing. Why would you need that? In my opinion the answer is simple: it can be useful for code migration to newer technologies or a completely different frontend tech stack!

This article is the first part of bigger series that will introduce you to the concept of framework agnostic UI approach. So stay tuned! The next articles will be published in the following weeks.