DEV Community

Dharan Ganesan
Dharan Ganesan

Posted on

Day 93: Web components

Web development has evolved significantly over the years, with a constant quest for more modular, reusable, and maintainable code. Web Components provides a set of web platform APIs that enable the creation of custom, reusable, and encapsulated HTML elements.

HTML Templates 🎨

HTML Templates are the foundation of Web Components, providing a way to declare fragments of markup that can be cloned and inserted into the document later. This helps in creating modular and reusable pieces of UI.

<template id="my-template">
  <style>
    /* Styles specific to this template */
  </style>
  <div class="component">
    <p>Hello, <slot></slot>!</p>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Here, we've defined a template with a placeholder (<slot></slot>) that can be filled with content when the template is used.

<script>
  const template = document.getElementById('my-template');
  const clone = document.importNode(template.content, true);
  clone.querySelector('slot').textContent = 'World';
  document.body.appendChild(clone);
</script>
Enter fullscreen mode Exit fullscreen mode

Custom Elements 🚀

Custom Elements allow developers to create their own HTML elements with custom behavior. They encapsulate functionality, making it easy to reuse and share across different projects.

class MyComponent extends HTMLElement {
  constructor() {
    super();

    const shadowRoot = this.attachShadow({ mode: 'open' });

    // Use the template directly within the class
    shadowRoot.innerHTML = `
      <style>
        p {
          color: #3498db;
        }
      </style>
      <p>Hello, <slot></slot>!</p>
    `;
  }
}

customElements.define('my-component', MyComponent);
Enter fullscreen mode Exit fullscreen mode

Now, you can use <my-component> in your HTML:

<my-component></my-component>
Enter fullscreen mode Exit fullscreen mode

Shadow DOM 🔍

Shadow DOM provides encapsulation for your web components, shielding their styles and structure from the rest of the document. This ensures that styles and scripts in your component do not interfere with the rest of the page.

class MyComponent extends HTMLElement {
  constructor() {
    super();

    // Attach a shadow DOM to encapsulate styles and structure
    const shadowRoot = this.attachShadow({ mode: 'open' });

    shadowRoot.innerHTML = `
      <style>
        p {
          color: #3498db;
        }
      </style>
      <p>Hello, <slot></slot>!</p>
    `;
  }
}

customElements.define('my-component', MyComponent);
Enter fullscreen mode Exit fullscreen mode

Tips 💡

  1. Keep it modular: Break down complex UIs into smaller, manageable components to maximize reusability.
  2. Use Shadow DOM judiciously: Leverage Shadow DOM to encapsulate styles and scripts, preventing unintended interference.
  3. Explore component libraries: Many libraries, such as LitElement and Stencil, simplify the creation and management of Web Components.
  4. Events: Use Custom Events to establish communication between Web Components, enabling a more modular architecture.
  5. Lifecycle Hooks: Implement lifecycle hooks (e.g., connectedCallback, disconnectedCallback) to perform actions when a component is added or removed from the DOM.

Usage 🌐

Web Components find extensive use in various scenarios, from UI libraries to large-scale applications.

  • UI Libraries: Build modular UI components that can be easily shared and reused across projects.
  • Micro-frontends: Use Web Components to create self-contained, independently deployable parts of a larger application.
  • Third-Party Integration: Develop widgets or components that can be embedded seamlessly into other websites.

Top comments (3)

Collapse
 
dannyengelman profile image
Danny Engelman • Edited
  • No need to define the class, an anonymous class will do
  • super() sets AND returns the Web Component 'this' scope
  • attachShadow sets AND returns this.shadowRoot, no need to create an extra variable
  • so all can be chained

That makes the code:

 customElements.define('my-component', class extends HTMLElement {
  constructor() {
    super()
      .attachShadow({ mode: 'open' })
      .innerHTML = `<style>
                       p {
                            color: #3498db;
                           }
                      </style>
                      <p>Hello, <slot></slot>!</p>`;
  }
});
Enter fullscreen mode Exit fullscreen mode

More modular, and demonstrating:

  • MDN documentation "// Always call super first in constructor" is wrong:

  • Inline Event handlers are soo powerful in Web Components
    (where there is no need for multiple Event handlers on one element)

 customElements.define('my-component', class extends HTMLElement {
  constructor() {
    const element = (tag, props = {}) => Object.assign(document.createElement(tag), props);
    super()
      .attachShadow({ mode: 'open' })
      .append( 
        element( "STYLE" , {
          innerHTML : `p { color: #3498db  }`
         }),
        element( "P" , {
          innerHTML : `Hello, <slot></slot>!`,
          onclick : (evt) => alert("FOO!")
        })
      );
  }
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dhrn profile image
Dharan Ganesan

Thanks for your feedback, Danny! Your insights were invaluable, and will incorporated your suggestions to enhance.

Collapse
 
dhrn profile image
Dharan Ganesan