As a developer, you know that reusing code as much as possible is an important premise. This refers to modularization and reusability of your code. HTML5 Web components aim to achieve both things with the creation of custom elements.

One interesting feature about the 6th version of the Angular framework is that it enables developers to create custom elements. Basically, you can create Angular custom elements that are generated from Angular components. The resultant element carries a minified version of the framework.

The way that this is done is with a framework-agnostic approach, and the resultant element can be used in any simple web project without Angular.

The Building Blocks of HTML5 Web Components

Before getting into more details, let’s spend a little time getting to know HTML5 web components. Web components is a suite of different technologies allows the developer to create custom elements.

Web components are composed of three parts:

  • Custom elements: a set of JavaScript API that enables to define custom elements and their behavior.
  • Shadow DOM: This refers to the encapsulation of the element code so the styles and scripts defined in our element will be private and won’t cause issues with other parts of the app.
  • HTML templates: Using the <template> tag you can write markup that won’t be rendered till is referenced by some Javascript code and then appended to the DOM. This way you can create reusable code.

Let’s focus on the first ones.

What Are Custom Elements?

Elements that are encapsulated, have their own functionality and can be reused in any web app. Version 5 of HTML comes with custom elements that provide a way for developers to define and register their own and fully featured DOM elements. Custom Elements allows web developers to create new custom HTML tags or extend the ones that already exist. The web components API defines a web strategy based on standards to create reusable components just using HTML, CSS, and JavaScript. As a result, you get a more modularized and reusable code and you can reduce the size of your it.

Angular Custom Elements

The Angular component is parsed by the creation API looking for input properties and defines the corresponding attributes for the custom element to be generated. Since HTML does not recognize case distinctions, the API transforms the property names to make them compatible with the custom elements attributes.

For example, the component input @Input() inputName becomes the attribute“input-name” for the custom element.

On the other side, the component outputs are transformed into HTML custom events, using the name of the output as the name of the event.

For example:

@Output() clickEvent = new EventEmitter<string>()

The custom element would generate the event “clickEvent”.
The emitted data will be available in the event’s detail property.

Creating an Example Application

Let’s do an example project to demonstrate how to create a custom element and use it outside an Angular project. For this simple task, I’m going to develop a button component that will register how many times it was clicked, and emit an event every time a new click happens.
The resulting Angular app will output this:
This is image title

Add Elements Dependency

To create custom elements with angular you’ll need the @angular/elements package.

	
ng add @angular/elements

This package exports the method createCustomElement that provides a bridge from Angular’s component interface and change detection functionality to the built-in DOM API.

Create a Custom Element

Now let’s create the component that will have the functionality of the custom button.
The component will have all of the source code in one file. The template and the styles together with the typescript definition.

ng g component custom-button --inline-style --inline-template -v Native

ViewEncapsulation.Native property is used to prevent the styles of the component from interfering with other elements. By using this property, the browser’s native implementation of shadow DOM will be used to render the component. A bundle would be generated containing the component class code, the template, and styles in a single file.

Define the Component Properties

For the custom button component we are going to define the following properties:

  • text Input: the text to display inside the button
  • countChanged Output: the function that will handle the event being triggered

custom-button.component.ts

import { Component, OnInit, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'custom-button',
  template: `<button type="button" class="btn" (click)="countClicks()">{{text}}</button>`,
  styles: [` 
    .btn {
      display: inline-block;
      font-weight: 400;
      text-align: center;
      white-space: nowrap;
      vertical-align: middle;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      border: 1px solid transparent;
      padding: .375rem .75rem;
      font-size: 1rem;
      line-height: 1.5;
      border-radius: .25rem;
      transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
      color: #fff;
      background-color: #28a745;
      border-color: #28a745;
    }
    .btn:hover {
      color: #fff;
      background-color: #218838;
      border-color: #1e7e34;
      cursor: pointer;
    }
  `],
  encapsulation: ViewEncapsulation.Native
})
export class CustomButtonComponent implements OnInit {

  @Input() text = 'Custom Button';
  @Output() countChanged = new EventEmitter<number>();
  private totalClicks: number = 0;

  constructor() { }

  ngOnInit() {
  }

  countClicks() {
    this.totalClicks++;
    this.countChanged.emit(this.totalClicks);
  }

}

Now let’s create the code inside the app component to use the custom-button component so we can test that it works ok.

app.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Custom element example app';
  count: number = null;

  constructor() {}

  handleCountChanged($event) {
    this.count = $event;
  }

}

app.component.html

<div class="app-container">
  <div style="text-align:center">
    <h1>
      Welcome to {{ title }}!
    </h1>
    <img width="200" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
  </div>
  <custom-button [text]="'click me!'" (countChanged)="handleCountChanged($event)"></custom-button>
  <label>Total count: {{count}}</label>
</div>

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { CustomButtonComponent } from './custom-button/custom-button.component';

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

Add the Component to NgModule

Nothing different there that all the components that you have created in the past, but let’s now register the new component in NgModule.

First of all, remove the bootstrap array which has the AppComponent.

Angular will need to compile it so you need to put it on the entryComponent list. And you’ll use the createCustomElement method from the @angular/elements package.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { AppComponent } from './app.component';
import { CustomButtonComponent } from './custom-button/custom-button.component';

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

  constructor(private injector: Injector) {
    const customButton = createCustomElement(CustomButtonComponent, { injector });
    customElements.define('custom-button', customButton);
  }

}

Now you can also see the ngDoBootstrap method that is used to tell Angular to use this module for bootstrapping.

Build and Check Output

First of all, let’s create a simple HTML page in order to see the custom element working.

indexTest.html

Now you can also see the ngDoBootstrap method that is used to tell Angular to use this module for bootstrapping.

Build and Check Output

First of all, let’s create a simple HTML page in order to see the custom element working.

indexTest.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Test Page</title>
  <script src="output.js"></script>
</head>
<body>
  <custom-button text="Click me!"></custom-button>
  <script>
    const button = document.querySelector('custom-button');
    button.addEventListener('countChanged', (event) => {
      console.log('Count changed event', event.detail);
    });
  </script>
</body>
</html>

Then let’s modify our package.json file, particularly the scripts, so you can generate the bundle for the custom element that would be later included in our test page.

package.json

"scripts": {
    ...
    "build": "ng build --prod --output-hashing=none",
    "concat": "concat -o output.js ./dist/ExampleApp/runtime.js ./dist/ExampleApp/polyfills.js ./dist/ExampleApp/scripts.js 
./dist/ExampleApp/main.js"
    ...
  }

What the –output-hashing=none do is simply to remove the hashes from the names from the generated JavaScript files. Those aren’t needed since you are going to concatenate them to use it only in one file.

The build output would be in the dist folder and you’ll see there 4 files runtime.js, polyfills.js, scripts.js, and main.js. Those are the ones that have all the source code related to our just created custom-button.

In order to use it in your already created indexTest.html page you need to concatenate them so you’ll end up importing just one file (output.js).

For this purpose, you can use the npm package concat.
You can install it with the following command:

npm i concat

Then you only need to run:

	
npm run build

and

npm run concat

The resulting file called output.js size is only 227,6 kB, nothing bad considering it has Angular included in it.

Finally, you can open your indexTest.html page in your browser in order to see the example with the custom button element working.
This is image title

Customization in Angular

As you can see building custom elements with Angular is pretty straight forward. Although at the moment there are some manual things that you need to do, it ends up being a simple task. Also as you can see the size of the output file is quite small, considering that it has Angular dependencies inside of it. Let’s hope that in the future releases this process gets even easier.

Originally published by Engineering Team at gistia.com

#angular #web-development

Building Custom Elements in Angular
4 Likes26.70 GEEK