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.
Prerequisites:
- Some familiarity with the TypeScript framework
- Basic working experience with the Angular framework
- Node.js and NPM installed on your development machine
What are Angular components
Image loading...
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...
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.
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.
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>
<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.
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
$$
@angular/cli
v7.3.9 was installed on the system.
Creating an Angular Project
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
$$
- When asked for Angular routing, type N as we don't need routing for your example.
- 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.
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:
jsimport { 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 { }
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:
jsimport { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class DataService { constructor(private httpClient: HttpClient) { } }
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
:
jsexport class DataService { apiKey = 'YOUR_API_KEY';
The last step is defining a get()
method that will fetch the news data from the API:
jsget(){ 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:
jsimport { 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() { } }
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:
jsexport class NewsComponent implements OnInit { articles;
Next, update the ngOnInit()
method as follows:
jsexport class NewsComponent implements OnInit { articles; constructor(private dataService: DataService) { } ngOnInit() { this.dataService.get().subscribe((data)=>{ console.log(data); this.articles = data['articles']; }); } }
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:
jsimport { 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:
jsexport 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
:
jsexport class AppModule { constructor(private injector: Injector) { const el = createCustomElement(NewsComponent, { injector }); customElements.define('news-widget', el); } ngDoBootstrap() {} }
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:
- Create a new folder.
- Go to the folder and create an
index.html
file. - Copy the JS files mentioned above from the
/dist
folder of the Angular project - 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>
<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...
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
$$
Inside the root of your project, create a build-component.js
file and add the following code:
jsconst 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();
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
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>
<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...
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.