Friday, June 25, 2021

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 builtin JavaScript APIs designed to define custom elements. Custom Elements are another name for web components.
  • Shadow DOM: A set of builtin JavaScript APIs for creating and attaching a private DOM tree to an element. This will allow you to create custom elements which 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 builtin 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 builtin JavaScript APIs to create custom elements or use existing libraries that abstracts 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 to use 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.

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.

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

As 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 continue 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 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.

Technically, an Angular component is a TypeScript class that’s decorated with the @Component decorator which takes a few meta data 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.

No comments:

Post a Comment

Technology’s generational moment with generative AI: A CIO and CTO guide

 Ref:  A CIO and CTO technology guide to generative AI | McKinsey 1. Determine the company’s posture for the adoption of generative AI As us...