Creating Reusable Web Component And Using It With Angular

In this post, I want to talk about the web components (Reusable) and why they are very useful to an organization, especially a large one. Many large organizations often consolidate their front-end code to pattern library for ensuring the consistency.

A pattern library is extremely useful when a company grows and splits into multiple teams but it also comes with some challenges, like different teams working with different front-end frameworks (like Angular, React, VUE etc). Then, how do you build the pattern library that works for all?

  1. One can’t choose any one framework because then you would be locked and doesn’t support all frameworks.
  2. Writing all code to vanilla HTML/js/cs is quite difficult.

So the solution to both the problems would be a custom element.

Web components are based 4 types,

  1. Custom Elements
  2. Shadow Dom
  3. Html imports
  4. Html Template

So in this post, we will first write our very first and very basic web component which is just for increment - decrement the value. Then, we will see how we can use that newly created web component to our Angular project (In the end, it is just a JS file so you can use it in any of your favorite JavaScript frameworks, like React, Angular,v Vue.js etc.). Doesn’t it sound awesome guys!!!

Let’s dive into creating first custom component

A simple counter will look like this.


Simple-Counter component can be used like this.

  1. <simple-counter [min]=”settings.min” [max]=”settings.max” [step]=”settings.step” (maxReached)=”handleMaxReached()” (minReached)=”handleMinReached()”>  
  2. </simple-counter>  

We will be building one custom component which is having two buttons and one label to display the current value. We are creating four properties of that element.

  1. Min
  2. Max
  3. Step
  4. Value

And, two custom events were dispatched when trying to extends the min and max limit. And two events or callbacks for increment and decrement when + or — button is pressed. It is as simple as a normal counter which will increment the value by step by step when + button is pressed and vice versa when — button is pressed.

Now, let’s create one file inside the demo directory and name it as simple-counter.js.

  1. (function() {  
  2.   const template = document.createElement('template');  
  3.   
  4.   template.innerHTML = `  
  5.     <div>  
  6.       <button type="button" incr>+</button>  
  7.       <span></span>  
  8.       <button type="button" decr>-</button>  
  9.     </div>  
  10.   `;  
  11.   
  12.   class SimpleCounter extends HTMLElement {  
  13.     constructor() {  
  14.       super();  
  15.   
  16.       this.increment = this.increment.bind(this);  
  17.       this.decrement = this.decrement.bind(this);  
  18.   
  19.       this.attachShadow({ mode: 'open' });  
  20.       this.shadowRoot.appendChild(template.content.cloneNode(true));  
  21.   
  22.       this.incrementBtn = this.shadowRoot.querySelector('[incr]');  
  23.       this.decrementBtn = this.shadowRoot.querySelector('[decr]');  
  24.       this.displayVal = this.shadowRoot.querySelector('span');  
  25.   
  26.       this.maxReached = new CustomEvent('maxReached');  
  27.       this.minReached = new CustomEvent('minReached');  
  28.     }  
  29.   
  30.     connectedCallback() {  
  31.       this.incrementBtn.addEventListener('click'this.increment);  
  32.       this.decrementBtn.addEventListener('click'this.decrement);  
  33.   
  34.       if (!this.hasAttribute('value')) {  
  35.         this.setAttribute('value', 1);  
  36.       }  
  37.     }  
  38.   
  39.     increment() {  
  40.       const step = +this.step || 1;  
  41.       const newValue = +this.value + step;  
  42.   
  43.       if (this.max) {  
  44.         if (newValue > +this.max) {  
  45.           this.value = +this.max;  
  46.           this.dispatchEvent(this.maxReached);  
  47.         } else {  
  48.           this.value = +newValue;  
  49.         }  
  50.       } else {  
  51.         this.value = +newValue;  
  52.       }  
  53.     }  
  54.   
  55.     decrement() {  
  56.       const step = +this.step || 1;  
  57.       const newValue = +this.value - step;  
  58.   
  59.       if (this.min) {  
  60.         if (newValue < +this.min) {  
  61.           this.value = +this.min;  
  62.           this.dispatchEvent(this.minReached);  
  63.         } else {  
  64.           this.value = +newValue;  
  65.         }  
  66.       } else {  
  67.         this.value = +newValue;  
  68.       }  
  69.     }  
  70.   
  71.     static get observedAttributes() {  
  72.       return ['value'];  
  73.     }  
  74.   
  75.     attributeChangedCallback(name, oldValue, newValue) {  
  76.       this.displayVal.innerText = this.value;  
  77.     }  
  78.   
  79.     get value() {  
  80.       return this.getAttribute('value');  
  81.     }  
  82.   
  83.     get step() {  
  84.       return this.getAttribute('step');  
  85.     }  
  86.   
  87.     get min() {  
  88.       return this.getAttribute('min');  
  89.     }  
  90.   
  91.     get max() {  
  92.       return this.getAttribute('max');  
  93.     }  
  94.   
  95.     set value(newValue) {  
  96.       this.setAttribute('value', newValue);  
  97.     }  
  98.   
  99.     set step(newValue) {  
  100.       this.setAttribute('step', newValue);  
  101.     }  
  102.   
  103.     set min(newValue) {  
  104.       this.setAttribute('min', newValue);  
  105.     }  
  106.   
  107.     set max(newValue) {  
  108.       this.setAttribute('max', newValue);  
  109.     }  
  110.   
  111.     disconnectedCallback() {  
  112.       this.incrementBtn.removeEventListener('click'this.increment);  
  113.       this.decrementBtn.removeEventListener('click'this.decrement);  
  114.     }  
  115.   }  
  116.   
  117.   window.customElements.define('simple-counter', SimpleCounter);  
  118. })();  

Configuration

Now, for your custom element to work on every browser, you will need to add polyfills for it.

npm install @webcomponents/webcomponentsjs

Then simply add one import statement to your Angular’s polyfills.ts file as shown below.

  1. /*************************************************************************************************** 
  2.  
  3. * Zone JS is required by Angular itself. 
  4.  
  5. */  
  6. import‘ zone.js / dist / zone’; // Included with Angular CLI.  
  7. import‘ @webcomponents / webcomponentsjs / webcomponents - sd - ce.js’;  
  8. /***************************************************************************************************  

Now, in app.module.ts file, import CUSTOM_ELEMENTS_SCHEMA from @angular/core and also add the schemas array in NgModule and also import the simple-counter.js file as shown below.

  1. import {  
  2.     BrowserModule  
  3. } from‘ @angular / platform - browser’;  
  4. import {  
  5.     NgModule,  
  6.     CUSTOM_ELEMENTS_SCHEMA  
  7. } from‘ @angular / core’;  
  8. import {  
  9.     AppComponent  
  10. } from‘. / app.component’;  
  11. import‘. / demo / simple - counter.js’;  
  12. @NgModule({  
  13.     declarations: [AppComponent],  
  14.     schemas: [CUSTOM_ELEMENTS_SCHEMA],  
  15.     imports: [BrowserModule],  
  16.     providers: [],  
  17.     bootstrap: [AppComponent]  
  18. })  
  19. export class AppModule {}  

How to Use simple-Counter

app.component.html

  1. <simple-counter [min]="min" [max]="max" [step]="step" (maxReached)="handleMaxReached()" (minReached)="handleMinReached()">  
  2. </simple-counter>  

 app.component.ts

  1. import { Component } from "@angular/core";  
  2.   
  3. @Component({  
  4.   selector: "app-root",  
  5.   templateUrl: "./app.component.html",  
  6.   styleUrls: ["./app.component.css"]  
  7. })  
  8. export class AppComponent {  
  9.   title = "app";  
  10.   
  11.   min = 0;  
  12.   max= 10;  
  13.   step = 1;  
  14.   
  15.   handleMaxReached() {  
  16.     alert("max reached");  
  17.   }  
  18.   
  19.   handleMinReached() {  
  20.     alert("min reached");  
  21.   }  
  22. }  

Download Source code from here

That’s it. I hope, it will be helpful to you.

Thanks for reading!!!


Similar Articles