How to build web components from Angular elements

How to build web components from Angular elements

In this tutorial we’ll learn about web components and using Angular to build a simple web component from scratch.

Success

Prerequisites:

  • Some familiarity with the TypeScript framework
  • Basic working experience with the Angular framework
  • Node.js and NPM installed on your development machine
Build, test, and deploy Angular with the easiest CI/CD tool for developers ever designed.
Try Buddy for Free

What are Angular components

Image loading...Angular Material UI (components) logo

Angular is a web platform for building frontend web applications. It makes use of a variety of concepts, including components, dependency injection, and data binding. A component controls a view which is a part of the screen. It encapsulates the code required to display the view and the logic that allows the user to interact with the view.

Angular components are reusable pieces of code that control a part of the application's UI. They are not web components per se, but Angular provides the elements package which helps export Angular components as web components. Technically, an Angular component is a TypeScript class that’s decorated with the @Component decorator which takes a chunk of metadata to specify the template, styles, and other things, that will be used by the component.

Before we get down to building the example, let’s first introduce the web components.

What are web components

Image loading...Official Web Components logo

Web components are a set of browser APIs that enable developers to create custom and reusable HTML tags. These tags can be used in web apps just like standard HTML tags. Since web components are built on top of standards, they can work across all modern web browsers.

Success
In short: Web components promote code reusability, maintainability, and interoperability, making it easier to build and maintain complex web applications.

Web components are based on three main technologies:

  • Custom Elements: A set of various built-in JavaScript APIs designed to define custom HMTL elements with their own properties, methods, and events. Custom elements are the key feature of the Web Components Suite.
  • Shadow DOM: A set of built-in JavaScript APIs for creating and attaching a private DOM tree to an element. This will allow you to create custom elements (e.g. markup and styles) which are isolated from the rest of the HTML document.
  • HTML Templates: This provides developers with elements like <template> and <slot> to create reusable templates.
Hint
On top of these elements, developers often utilize ES modules to import and export the JavaScript code that defines the behavior of web components.

Creating and using web components

You can create custom web components using JavaScript and a set of built-in methods such as customElements.define(). You can then use the custom element just like you would normally use any HTML tag. For example if AppMenu is the name of our custom element, we can use it as follows in our HTML document:

html
<AppMenu></AppMenu>
Tip
When creating custom elements, choose names that accurately reflect their purpose or functionality. For example, if you're creating a custom element for a navigation menu, you could name it <custom-nav> or <custom-menu>.

Libraries and web components

Custom elements can be created either using the browser's JavaScript APIs, or by using existing libraries that abstract away the complexities involved in creating web components with vanilla JavaScript. Some popular libraries include:

  • Stencil: an open source web components compiler that generates standards-compliant web components. It allows you to use TypeScript API to build components just like you would use a modern framework such as Angular or React. It’s created and maintained by the Ionic team and was used to create the Ionic UI's web components.
  • Polymer: A Google-driven project providing a set of tools for creating custom elements.
  • Slim.js: An open source lightweight web components library that provides modern features, such as data binding.
Tip
Learn how to create a CI/CD pipeline in Buddy, that will build, test and deploy your Angular application on a single push to a branch.

Installing Angular CLI

With everything explained, it's time to get down to work. First, we need to install the Angular CLI that will allow us to generate and work with Angular projects.

Open the terminal and run the following command:

bash
$ npm install -g @angular/cli $$
Hint
At the time of this writing, @angular/cli v7.3.9 was installed on the system.

Creating an Angular Project

Success
All source code written in this guide is available at https://github.com/buddy-works/tutorials-angular-web-components.

After installing Angular CLI, you can now proceed to create a new project for our Angular app. Head back to your terminal and run the following command:

bash
$ ng new angular-web-component $$
  1. When asked for Angular routing, type N as we don't need routing for your example.
  2. When asked for the stylesheet, select CSS.

Wait for your project dependencies to get installed, and proceed to the next section of this tutorial.

Installing & setting up Angular elements

According to the Angular Elements documentation, the @angular/elements package exports a [createCustomElement](https://angular.io/api/elements/createCustomElement)``() API that provides a bridge from Angular's component interface and change detection functionality to the built-in DOM API.

Angular elements allow you to transform an Angular component to custom elements (web components). You can easily create a custom element and the package will take care of converting the required Angular functionality to the corresponding native HTML APIs.

You can set up Angular elements in your Angular project very easily using the ng add command. Head back to your terminal, navigate to your project’s folder and invoke the ng add @angular/elements command:

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

The command will take care of installing the @angular/elements package and updating the necessary files automatically.

At the time this guide was written, Angular elements had a bug that causes an Uncaught TypeError with the message: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.

This issue can be solved by opening the tsconfig.json file and changing the target to es2015:

js
{ "compileOnSave": false, "compilerOptions": { "baseUrl": "./", "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, "module": "es2015", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "target": "es2015", "typeRoots": [ "node_modules/@types" ], "lib": [ "es2018", "dom" ] } }

This will tell the TypeScript compiler to compile your source code to ES6 instead of ES5 which means you will not be able to target old browsers including Internet Explorer.

Hint
If this solution is not an option, you can apply other fixes that you can find from this GitHub issue.

Creating an Angular service

In this part, we are going to configure an Angular service that will fetch data from the news API at NewsAPI.org so that it can be displayed by our component. Head back to your terminal and run the following command to generate a service:

bash
$ ng generate service data $$

We’ll also be using HttpClient to send GET requests to the third-party API. This way, we don't have to import HttpClientModule in our application module. Open the src/app.module.ts file and update it accordingly:

js
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { HttpClientModule } from "@angular/common/http"; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Hint
How it works: HttpClientModule is imported from the @angular/common/http package and added to the imports array of the module metadata.

Next, open the src/app/data.service.ts file and update accordingly:

js
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class DataService { constructor(private httpClient: HttpClient) { } }
Hint
How it works: HttpClient is imported from the @angular/common/http package and injected as httpClient via the service constructor.

Now we need a way to access NewsAPI.org to retrieve news data. Fortunately, the service offers a free plan for open source and development projects, so you can go their website and register for an API key at no charge.

With the key registered, you can define a variable called apiKey:

js
export class DataService { apiKey = 'YOUR_API_KEY';

The last step is defining a get() method that will fetch the news data from the API:

js
get(){ return this.httpClient.get(`https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=${this.apiKey}`); }

Creating an Angular component

The data fetching service is configured. Now, we need an Angular component that will be built as a web component to display it.

Head back to your terminal and run:

bash
$ ng generate component news $$

Next, open the src/app/news/news.component.ts file and import the data service:

js
import { Component, OnInit } from '@angular/core'; import { DataService } from '../data.service'; @Component({ selector: 'app-news', templateUrl: './news.component.html', styleUrls: ['./news.component.css'] }) export class NewsComponent implements OnInit { constructor(private dataService: DataService) { } ngOnInit() { } }
Hint
How it works: The service is injected as dataService via the component constructor.

Now, we're going to retrieve the news in the ngOnInit event of the component.

Begin with adding an articles variable that will hold the news after retrieving them from the API:

js
export class NewsComponent implements OnInit { articles;

Next, update the ngOnInit() method as follows:

js
export class NewsComponent implements OnInit { articles; constructor(private dataService: DataService) { } ngOnInit() { this.dataService.get().subscribe((data)=>{ console.log(data); this.articles = data['articles']; }); } }
Hint
How it works: We call the get() method of DataService which returns an RxJS Observable. We then subscribe to the returned Observable to actually send the GET request to the API. Finally, the data is added to the articles variable.

To display the news data from the API open the src/app/news/news.component.html file and update it with these HTML elements:

html
<div *ngFor="let article of articles"> <h2>{{article.title}}</h2> <p> {{article.description}} </p> <a href="{{article.url}}">Read full article</a> </div>

Testing component

Let’s now test if our component is working properly. Open the src/app/app.component.html file, remove all the content, and add:

html
<app-news></app-news>

Go back to your terminal and run this command to serve your app locally:

bash
$ ng serve $$

Your application will be available from the http://localhost:4200/ address. If you visit that address with your web browser, you should see a list of news displayed.

Transforming Angular component to web component

Until now, we only have a component which only works inside an Angular project. However, our goal is to transform the news component to a custom element, so that it can be used outside of the Angular app project in any JavaScript application.

Open the src/app.module.ts file and start by adding the following imports:

js
import { Injector} from '@angular/core'; import { createCustomElement } from '@angular/elements';

Next, add NewsComponent to the bootstrap array of the module:

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

Next, inject Injector via the module constructor:

js
export class AppModule { constructor(private injector: Injector) {} }

Finally, call the createCustomElement() method to transform the component to a custom element that we adequately called news-widget:

js
export class AppModule { constructor(private injector: Injector) { const el = createCustomElement(NewsComponent, { injector }); customElements.define('news-widget', el); } ngDoBootstrap() {} }
Success
That’s it! This is all the required code to build a custom element from our Angular component.

Building web component

After adding the code for transforming our Angular component to a custom element, let’s now build the web component, so we can use it in other projects without depending on Angular.

Head back to your terminal and run the following command from the root of your project:

bash
$ ng build --prod --output-hashing none $$

This command will build the project for production and will create a dist/angular-web-component folder with the built files. We only need the following JavaScript files to use our web component:

runtime.js es2015-polyfills.js polyfills.js scripts.js main.js

Using web component

After compiling our project and getting a bunch of JavaScript files, let’s see how we can use web components outside of Angular.

First, we need to check if the component works properly:

  1. Create a new folder.
  2. Go to the folder and create an index.html file.
  3. Copy the JS files mentioned above from the /dist folder of the Angular project
  4. Open the index.html file and add the following HMTL elements:
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Testing the News Web Component</title> <base href="/"> </head> <body> <news-widget></news-widget> <script type="text/javascript" src="runtime.js"></script> <script type="text/javascript" src="es2015-polyfills.js" nomodule></script> <script type="text/javascript" src="polyfills.js"></script> <script type="text/javascript" src="scripts.js"></script> <script type="text/javascript" src="main.js"></script> </body> </html>
Hint
How it works: The component files are imported with the <script> tag. The component itself is called using the <news-widget> tag.

Let us serve this file now. We’ll use the serve package from npm which provides a simple local HTTP server. Go to your terminal and run the following command to install the package:

bash
$ npm install -g serve $$

Next, make sure you are inside the folder where you have created the index.html file and run the following command:

bash
$ serve $$

Your app will be available from the http://localhost:5000 address. If you visit that address with your web browser, you should see your news component displayed which means the web component is exported successfully:

Image loading...Displayed news component

Concatenating web component files

To be able to use our web component, we need to include the five JavaScript files which – let's be honest – is not really convenient. The solution is to concatenate all these files into one JS file.

First, run the following command from the root of your Angular project to install the concat and fs-extra packages:

bash
$ npm install --save-dev concat fs-extra $$
Hint
These two packages will be used to work with the filesystem and concatenate the files.

Inside the root of your project, create a build-component.js file and add the following code:

js
const fs = require('fs-extra'); const concat = require('concat'); build = async () =>{ const files = [ './dist/angular-web-component/runtime.js', './dist/angular-web-component/polyfills.js', './dist/angular-web-component/es2015-polyfills.js', './dist/angular-web-component/scripts.js', './dist/angular-web-component/main.js' ]; await fs.ensureDir('widget'); await concat(files, 'widget/news-widget.js'); } build();
Warning
Make sure that your project is named angular-web-component (or update the path accordingly) and that you put the right paths for the JS files in the files array.

Next, add the following script to the package.json file of your Angular project:

js
"scripts": { "build:component": "ng build --prod --output-hashing none && node build-component.js", },

Finally, you can run your script to build your project and concatenate the files into one JavaScript file that can be used wherever you want to use your web component to display news. Head back to your terminal and run the following command:

js
$ npm run build:component
Tip
You can use Buddy to automatically build and test your Angular application to save time and ensure everything is working correctly. Image loading...Example Angular pipeline

When the process has finished, in your app root you should find a /widget folder with the news-widget.js file. Go to the /widget folder, create an index.html file, and add the following HTML elements:

html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Testing the News Web Component</title> <base href="/"> </head> <body> <news-widget></news-widget> <script type="text/javascript" src="news-widget.js"></script> </body> </html>
Hint
You can see we only use one file in the <script> tag instead of 5 different JS files.

The last thing is running the serve command again in the widget folder: you should see your web component with news data from the API at the http://localhost:5000 address! 😎

Image loading...News feed preview

Summary

Throughout this tutorial, we’ve introduced web components (also called custom elements) to Angular developers. Next, we’ve used the Angular elements package to build a web component from an Angular component used to fetch news data from a third-party news API. This component can be used outside the Angular project in any JavaScript based project.


Additional resources

Read similar articles