Exploring Web Components: Building Reusable UIs for Web Development 🌐👨‍💻
Tags: webcomponents, webdev, html
Published: 2024-01-28 20:41:03

Even though Web Components have been around for a while, I've never used them in my projects, and I've never seen my colleagues use them either! Have you ever tried using Web Components?

Web Components emerged around 2011 as a collection of web technologies aimed at making reusable custom elements for web development. These components include Custom Elements, Shadow DOM, HTML Templates, and HTML Imports (though the latter is now deprecated). They've evolved over time through collaboration between browser vendors, web standards bodies, and the web development community. With Web Components, we can create encapsulated custom elements with specific functionality and use them in our web applications to promote reusability and maintainability.

## Concepts and usage 🚀✨

As developers, we're aware that reusing code whenever possible is beneficial. However, custom markup structures, like complex HTML for custom UI controls, have traditionally posed challenges for reuse. Using them multiple times can clutter our page with tangled HTML, styles, and scripts if not managed carefully.
Web Components aim to address these challenges by offering three main technologies that work together to create flexible custom elements. These elements encapsulate their functionality, allowing for easy reuse throughout our projects without worrying about code conflicts.

### 1) Custom elements 💡
A set of JavaScript APIs that allows us to define custom elements and their behavior, which can then be used as desired in our user interface.
- Custom Elements enable developers to define their own custom HTML elements with associated behavior.
- We can create new HTML tags that encapsulate specific functionality and use them just like standard HTML elements.
- Custom Elements are defined using the class syntax and extending the `HTMLElement` class.

```js
class MyComponent extends HTMLElement {
  constructor() {
    super();
    // Initialization code
  }
}

customElements.define('my-component', MyComponent);
```

### 2) Shadow DOM 💡
The Shadow DOM API comprises JavaScript APIs that allows us to attach an encapsulated "shadow" DOM tree to an element. This shadow DOM tree is rendered independently from the main document DOM, keeping the element's features private. By doing so, we can script and style the element without worrying about conflicts with other parts of the document. This encapsulation ensures that the element's functionality remains contained and isolated, enhancing maintainability and reducing the risk of unintended interactions.
- The Shadow DOM allows us to encapsulate the internal structure and styling of a custom element.
- It provides a scoped DOM tree, isolated from the rest of the document, preventing styles and scripts from leaking into or out of the component.

```js
class MyComponent extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = '<p>This is inside the shadow DOM</p>';
  }
}

customElements.define('my-component', MyComponent);
```

### 3) HTML templates 💡
The `<template>` and `<slot>` elements enable us to write markup templates that are not displayed in the rendered page. These can then be reused multiple times as the basis of a custom element's structure.
- HTML Templates allow developers to declare fragments of HTML that can be cloned and inserted into the DOM dynamically.
- Templates are often used in conjunction with Custom Elements to define the internal structure of a component.

```html
<template id="my-template">
  <style>
    /* Styles for the template */
  </style>
  <div>
    <p>This is a template content.</p>
  </div>
</template>
```

### 4) HTML Imports (deprecated)
HTML Imports were initially part of the Web Components specification but have been deprecated in favor of using ES6 modules and other module systems. Developers are encouraged to use modern module systems and tools for managing dependencies.

So the basic approach for implementing a web component generally looks something like this:
1. Create a class in which we specify our web component functionality, using the class syntax.
2. Register our new custom element using the `CustomElementRegistry.define()` method, passing it the element name to be defined, the class or function in which its functionality is specified, and optionally, what element it inherits from.
3. If required, attach a shadow DOM to the custom element using `Element.attachShadow()` method. Add child elements, event listeners, etc., to the shadow DOM using regular DOM methods.
4. If required, define an HTML template using `<template>` and `<slot>`. Again use regular DOM methods to clone the template and attach it to our shadow DOM.
5. Use our custom element wherever we like on our page, just like we would any regular HTML element.

## Working Example 👨‍💻🔥

We'll define a custom HTML element called `<my-button>` that displays a button with customizable text.

```js
<!-- button.js -->
const template = document.createElement("template");
template.innerHTML = `
   <style>
      /* Styles for the button */
      :host {
         display: inline-block;
      }
      button {
         padding: 10px 20px;
         font-size: 16px;
         background-color: #ffa500;
         color: #fff;
         border: none;
         border-radius: 5px;
         cursor: pointer;
      }
      button:hover {
         background-color: #f5ab25;
      }
   </style>
   <button><slot></slot></button>`

export class MyButton extends HTMLElement {
   constructor() {
      super();
      // Create a shadow root
      const shadowRoot = this.attachShadow({ mode: 'open' });
     // Clone the template content and append it to the shadow DOM
      const instance = template.content.cloneNode(true);
      shadowRoot.appendChild(instance)
   }

   // connectedCallback(){ }
}

customElements.define('my-button', MyButton); //NOTE: The name must have atleast one hyphen(-) in it.
```

Now, we can use the `<my-button>` custom element in any HTML file.

```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Custom Button - Web Component</title>
      <script defer type="module" src="button.js"></script>
   </head>
   <body>
      <my-button>Hi, I am Custom Button!</my-button>
   </body>
</html>
```

Open `index.html` in a web browser, and we should see a button labeled "Hi, I am Custom Button!". This button is rendered using the `<my-button>` custom element defined in `button.js`.


![Final Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p7pgb6mmq1dbnnezhq71.png)

## Final Thoughts 🤔💡

In summary, traditional web development using plain HTML involves manual coding and styling of UI elements, whereas Web Components offer a structured and modular approach. With encapsulated behavior and styling, thanks to features like shadow DOM, Web Components lead to more maintainable and scalable codebases. Additionally, they provide a standardized method for creating reusable and interoperable components across various frameworks and libraries. While adoption may vary, Web Components offer powerful tools for building modular and maintainable web applications.

Happy Coding! 🚀👨‍💻✨