Building Web Components with Angular
In this tutorial, we’ll learn about web components and we’ll use Angular to build a simple web component.
Angular is a web platform for building frontend web applications. It makes use of various concepts such as components, dependency injection and data binding.
Angular components are reusable pieces of code that control a part of the application UI. They are not web components but Angular provides the elements
package which helps export Angular components as web components.
Before getting started to build the example let’s first introduce web components.
Introducing Web Components
Web components are a set of browser APIs that enable developers to create custom and reusable HTML tags that 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.
Web components are based on four technologies:
- Custom Elements: A set of various built-in JavaScript APIs designed to define custom elements. Custom Elements are another name for web components.
- 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 that are isolated from the rest of the HTML document.
- ES Modules.
- HTML Templates: This provides developers with elements like
<template>
and<slot>
to create reusable templates.
You can create a custom web component 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:
<AppMenu></AppMenu>
You can either use the browser built-in JavaScript APIs to create custom elements or use existing libraries that abstract away all 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 using simply 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 it was used to create the Ionic UI components.
- Polymer: This project by google provides a set of tools for creating custom elements.
- Slim.js: An open-source lightweight web component library that provides modern features like data binding.
Prerequisites
In order to follow this tutorial, you will need to have a few prerequisites such as:
- Familiarity with TypeScript,
- Working experience with Angular,
- Node.js and NPM installed on your development machine. You can download both of them from the official website or you can use NVM to easily install Node on your system.
Installing Angular CLI
Now, if you have the required prerequisites, let’s proceed to install Angular CLI which allows you to generate Angular projects and work with them.
Open a new terminal and run the following command:
$ npm install -g @angular/cli
At the time of this writing, @angular/cli
v7.3.9 will be installed on your system.
Creating an Angular Project
After installing Angular CLI, you can now proceed to create a new Angular 7 project. Head back to your terminal and run the following command:
$ ng new angular-web-component
You will be asked if you Would you like to add Angular routing? Enter No (as we will not need routing in our example) and Which stylesheet format would you like to use? Choose CSS.
Wait for your project dependencies to get installed before continuing to the next section.
Installing & Setting up Angular Elements
According to the Angular Elements docs
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 allows you to transform an Angular component to a custom element (web component). 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 as follows:
$ cd angular-web-component
$ ng add @angular/elements
The command will take care of installing the @angular/elements
package (v7.2.15 as of this writing) and updating the necessary files automatically.
At this time, Angular Elements has 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.
You can solve this issue in many ways. For example, open the tsconfig.json
file and change the target to es2015
:
{
"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. So if this is not an option, you can apply other fixes that you can find from this GitHub issue.
What’s an Angular Component
Before transforming our Angular component to a web component, let’s first introduce Angular components and their related concepts.
So what’s an Angular component?
A component controls a view that 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.
Technically, an Angular component is a TypeScript class that’s decorated with the @Component
decorator which takes a few metadata for specifying the template and the styles, among other things, that will be used by the component.
Creating an Angular Service
We’ll be using an Angular service to fetch data from the news API at NewsAPI.org
that will be displayed by our component. So head back to your terminal and run the following command to generate a service:
$ ng generate service data
We’ll also be using HttpClient
to send GET requests to the third-party API so we need to import HttpClientModule
in our application module. Open the src/app.module.ts
file and update it accordingly:
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 { }
We simply import HttpClientModule
from the @angular/common/http
package and add it to the imports
array of the module metadata.
Next, open the src/app/data.service.ts
file and update accordingly:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private httpClient: HttpClient) { }
}
We import HttpClient
from the @angular/common/http
package and we inject it as httpClient
via the service constructor.
Note: We’ll be using a third-party API available from NewsAPI.org to retrieve news data. It offers a free plan for open source and development projects. So you first need to go to their website here to register for an API key.
Next, define a variable called apiKey
:
export class DataService {
apiKey = 'YOUR_API_KEY';
Next, define a get()
method that takes care of fetching the news data from the API:
get(){
return this.httpClient.get(`https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=${this.apiKey}`);
}
Creating an Angular Component
After creating the service, let’s create the Angular component that will be built as a web component.
Head back to your terminal and run the following command:
$ ng generate component news
Open the src/app/news/news.component.ts
file and import the data service as follows:
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() {
}
}
We inject the service as dataService
via the component constructor.
Next, let’s retrieve the news in the ngOnInit
event of the component.
First, add an articles
variable that will hold the news after retrieving them from the API:
export class NewsComponent implements OnInit {
articles;
Next, update the ngOnInit()
method as follows:
export class NewsComponent implements OnInit {
articles;
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.get().subscribe((data)=>{
console.log(data);
this.articles = data['articles'];
});
}
}
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 and finally we add the data to the articles
variable.
Next, open the src/app/news/news.component.html
file and update it as follows to display the news data from the API:
<div *ngFor="let article of articles">
<h2>{{article.title}}</h2>
<p>
{{article.description}}
</p>
<a href="{{article.url}}">Read full article</a>
</div>
Let’s now test if our component is working properly. Open the src/app/app.component.html
file, remove all the content and add:
<app-news></app-news>
Go back to your terminal and run the following command to serve your app locally:
$ 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 the Angular Component to A Web Component
Until now, we only have an Angular component that only works inside an Angular project but our goal is to transform the news component to a web component or custom element so that it can be used outside of the Angular project in any JavaScript application.
Open the src/app.module.ts
file and start by adding the following imports:
import { Injector} from '@angular/core';
import { createCustomElement } from '@angular/elements';
Next, add NewsComponent
to the bootstrap
array of the module:
@NgModule({
declarations: [
AppComponent,
NewsComponent
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent , NewsComponent]
})
export class AppModule {Next, inject `Injector` via the module constructor:
export class AppModule {
constructor(private injector: Injector) {}
}Finally, call the `createCustomElement()` method to transform the component to a custom element:
export class AppModule {
constructor(private injector: Injector) {
const el = createCustomElement(NewsComponent, { injector });
customElements.define('news-widget', el);
}
ngDoBootstrap() {}
}
That’s it! This is all the required code to build a custom element from our Angular component.
Building our 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:
$ 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 for using our web component:
runtime.js
es2015-polyfills.js
polyfills.js
scripts.js
main.js
Using our Web Component
After compiling our project and getting a bunch of JavaScript files, let’s see how we can use our web component outside of Angular.
Create an index.html
file inside some folder and copy the mentioned JavaScript files from the dist
folder of the Angular project in the same folder. Open the index.html
file and add the following code that will be used to test if our web component works properly:
<!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>
We simply import the component files using a <script>
tag and we call the component using the <news-widget>
tag ( this is the name we specified for our custom element in the customElements.define('news-widget', el)
method inside the constructor of AppModule
).
Now, let’s serve this file. 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:
$ 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:
$ 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:
Concatenating the Web Component Files
To be able to use our web component, we’ll need to include the five JavaScript files which is not really convenient so 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:
$ npm install --save-dev concat fs-extra
These two packages will be used to work with the file system and concatenate the files.
Inside the root of your project, create a build-component.js
file and add the following code:
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();
You need to make sure your put the right paths for the JavaScript files in the files
array. If you didn’t name your project angular-web-component
, the path will be different in your case depending on the name of your project.
Next, add a script to the package.json
file of your Angular project as follows:
"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:
$ npm run build:component
When the command is finished, you should find a widget folder in the root of your project with a news-widget.js
file.
Now, you can use this one file instead of including all the five JS files when you want to import your web component.
In the widget
folder, create an index.html
file and add the following content:
<!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>
As you can see, we are now including only one file then we call our news web component as before.
Head back to your terminal, navigate to the widget
folder and run the serve
command:
$ cd widget
$ serve
Go to the http://localhost:5000
address, you should see your web component with news data from the API.
Conclusion
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. You can find the source code of this project from this GitHub repository.