Building Framework-agnostic Web Components in Angular

0
4964

In a tech environment filled with polyglots who’re comfortable with various JavaScript frameworks (Angular, React, Vue, Ember, etc), let us understand how to develop reusable components and elements in Angular, which can be reused seamlessly across JavaScript frameworks.

As the old saying goes, the only constant in life is change. There are many JavaScript frameworks available today. These include Angular, React,

Vue.js, Ember, and many more. Initially, we choose a single framework and build the whole application in a sort of monolithic way. However, the reality is that, over time, we must embrace the pluralism in frameworks. The product we are working on becomes, quite obviously, a polyglot (conversant in many languages). The impending challenge is to build a component (element) in one framework and seamlessly reuse it in others. A Web component is the immediate solution. It is novel, easy-to-implement, easy-to-maintain and flexible enough to be used across JavaScript ES2015 compatible frameworks.

In this article, we are going to use Angular as the base framework. We will look at how to implement a reusable, framework-agnostic Web component from scratch. After bundling it as a JavaScript file, we will deploy and use it as well.

Why Web components
Web components can be built once in your favourite JavaScript framework, yet be used in every other framework, without any changes. Frameworks are not eternal; developers start with one and slowly move on to others. ‘Framework polyglots’ is the current reality. This is even more so with the ubiquitous presence of FOSS around us. The typical shelf life of a front-end framework in a product is around five years or so. After which another rich framework shows up, and the developers and community hop onto it merrily. There’s this eternal quest to move to something shinier, newer and better. There are, of course, many factors that influence such a move. Some are organic, whereas some are purely inorganic.

Organic factors: The product you’re working on merges or integrates with another product. The need to be a polyglot is organic in this case.

Inorganic: A company acquires another, or gets acquired, paving the way for inorganic polyglotism.
The mantra for staying afloat in such a multi-platform world is to build the elements/components once and let them be reused across similar frameworks seamlessly throughout. This is the reason for the advent and proliferation of Web components in today’s dynamic front-end Web frameworks.
Let us now dive into some technicalities.

Figure 1: The first Angular app output

Installing Angular
Angular is one of the most popular JavaScript front-end frameworks. In this section, let us look at how to install the latest version of Angular (Angular 9 was released in November 2019).
To begin, give the following command:

npm install -g @angular/cli

This installs Angular CLI’s latest version. Now type the following command:

ng new angular-web-component

This creates a new project under the $pwd/angular-web-component directory. Now give the following command:

cd angular-web-component
ng add @angular/elements

This adds the elements package required later for building the Web component. Next, type the following:

ng generate component MyWebComponent

The Angular component is named MyWebComponent. Now, open your favourite typescript editor (e.g., VS Code) and navigate to the angular-web-component directory. You can do it by typing ‘code .’ in $pwd. One can download VS Code from https://code.visualstudio.com/download. The next command is:

npm start

Now, open localhost:4200 in your Web browser. In the browser, you can see your first Angular application running. The screen will look something like what’s shown in Figure 1.

Developing an Angular component
Now that we have installed Angular and have a ‘hello world’ sort of application up and running, let’s look at how to modify it to something meaningful.

We will do a simple modification to show a REST API call, and render the REST API output in the GUI in a very simple manner. For the REST API part, we will call an open source REST API interface provided by https://jsonplaceholder.typicode.com/posts. It will return a tuple consisting of <postID, userId, topic>. We will render it in the GUI with a very simple HTML table, with no fancy grid.

We need to modify the files under the ‘src/app/my-web-component’ directory as follows.

File: my-web-component.component.html

<div style=”margin: 10px; text-align: center;”>
<h2> Posts: </h2>
<table class=”table-bordered”>
<tr class=”inventory-table-header”>
<th class=”text-center”>Post ID</th>
<th class=”text-center”>User ID</th>
<th class=”text-center”>Title</th>
</tr>
<tr *ngFor=”let post of posts”>
<td>{{post.id}}</td>
<td>{{post.userId}}</td>
<td>{{post.title}}</td>
</tr>
</table>
</div>

File: my-web-component.component.ts

import { Component, OnInit } from ‘@angular/core’;
import { HttpClient } from ‘@angular/common/http’;
@Component({
selector: ‘app-my-web-component’,
templateUrl: ‘./my-web-component.component.html’,
styleUrls: [‘./my-web-component.component.scss’]
})
export class MyWebComponent implements OnInit {

public posts = [];

constructor(private httpClient: HttpClient) {
}

ngOnInit() {
this.httpClient.get(‘https://jsonplaceholder.typicode.com/posts’).subscribe((restApiOutput: any) => {
this.posts = restApiOutput;
});
}
}

That’s enough for the component. There’s no need to change the styling file (.scss) and the unit test file (.spec) for now.

Let’s change the main application to include the newly added Angular component MyWebComponent. For this, we need to come back to the src/app directory.

cd angular-web-component/src/app

File: app.component.html (delete everything, just keep the one line)

<app-my-web-component></app-my-web-component>

File: app.module.ts
import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule } from ‘@angular/core’;
import { HttpClientModule } from ‘@angular/common/http’;
import { AppComponent } from ‘./app.component’;
import { MyWebComponent } from ‘./my-web-component/my-web-component.component’;

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

Assuming your Angular app is still running (i.e., the last time when you did npm start, you just let it go, without stopping it by using Ctrl-C), you will see something like what’s shown in Figure 2, in the browser.
Now that we have the Angular component up and running, let’s look at how to turn it into a Web component.

Converting to a Web component
First, we are going to make four distinct changes in the app.module.ts file, as follows.

import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule, Injector } from ‘@angular/core’;
import { HttpClientModule } from ‘@angular/common/http’;
import { AppComponent } from ‘./app.component’;
import { MyWebComponent } from ‘./my-web-component/my-web-component.component’;
import { createCustomElement } from ‘@angular/elements’; // Change-4

@NgModule({
declarations: [
AppComponent,
MyWebComponent
],
imports: [
BrowserModule,
HttpClientModule,
],
providers: [],
bootstrap: [], // Change-1
entryComponents: [MyWebComponent] // Change-2
})
export class AppModule {
constructor(private injector: Injector) { } // Change-3

ngDoBootstrap() { // Change-4
const webComponent = createCustomElement(MyWebComponent, { injector: this.injector });
customElements.define(‘my-web-component’, webComponent);
}
}

Let’s look at the changes, one by one:

  1. Bootstrap is the ‘start point’ of the Angular app. Let’s make it empty. So, we do not have any Angular entry point for this app, as of now.
  2. Let’s put MyWebComponent into the entryComponents array. This means that now our Web component will be an entry point for the app.
  3. Inject the Injector in the constructor; this will be used later in the ngDoBootstrap method given below.
  4. A new method ngBootstrap is added. Following are the changes related to ngBootstrap:
  1. createCustomElement from @angular/elements translates the vanilla Angular component to a Web component, along with its dependencies.
  2. To register the newly created custom Web component to the browser, we are using the customElements.define method. The element is the webComponent and its handle is called my-web-component. Going forward, we shall use my-web-component to refer to this Web component.
Figure 2: How posts show up in GUI

Now, an important change needs to be made at the top level (i.e., inside the angular-web-component directory) in the tsconfig.json file. Let’s change the target variable from es5 to es2015. This is so that the browser supports custom Web components, natively.
That’s all. Now let’s build and test it!

Packaging the Web component
We need to package the Web component and all of its prerequisites into a wholesome JavaScript file. Let’s now do it in the package.json file as follows.
Note: If you’re in Mac or Linux, instead of jscat, you can simply use inbuilt cat.

For Windows, one needs to install jscat (or merge it). Please install jscat as follows:

npm install jscat

File: package.json (added the following two lines)

“scripts”: {
……………..
“build-prod”: “ng build --prod --output-hashing=none”,
“package”: “jscat ./dist/angular-web-component/runtime.js ./dist/angular-web-component/polyfills.js ./dist/angular-web-component/main.js > angular-web-component.js”
},

Now, to build up the angular-web-component.js file, let’s run the following command:

npm run build-prod && npm run package

This results in an angular-web-component.js file in the top-level root directory (i.e., inside the angular-web-component directory).

Testing it
Now it’s the time to see your handiwork in action! A simple demonstration will suffice. Let the application continue to run (i.e., let npm continue to run). Let’s change the index.html file as follows.

……………….
<body>
<my-web-component></my-web-component>
<script type=”text/javascript” src=”../angular-web-component.js”>
</script>
</body>
…………………..

Now you can see that the output shown in Figure 2 appears in the browser. Congrats – you’ve done it!
In this article, to tackle the evolving need for platform-agnostic front-end applications, we discussed the Web component that we could use. This makes the component reusable, framework-agnostic and interoperable across JavaScript frameworks. There are plenty of other evolving platforms too, which are specialised for building the Web components from the ground up, and are mostly FOSS. Google’s Polymer litElement is one of the pioneers here. Others are Stencil, Haunted, etc. Overall, things are pretty much still evolving, so do keep watching this space.

Note: The example code shown in this article can be found in github: https://github.com/pradip-interra/angular-web-component

LEAVE A REPLY

Please enter your comment!
Please enter your name here