UI Development

Web Components

 

Introduction

Web components are a set of different technologies that allows us to create reusable and encapsulated HTML tags with their functionality away from the rest of the code. Using web components developers are no longer limited to the existing HTML tags that the browser vendors provide.

Web components provides us with many benefits like reusability, readability, maintainability, consistency and interoperability.

Concepts and Usage

Web components consists of three main technologies.

  1. Custom Elements
  2. shadow DOM
  3. HTML Templates and Slots

Custom Elements

Custom Elements are the major building blocks of Web Components. Custom Elements are a set of different APIs that enables developers to extend HTML elements, build new ones and define their behavior. Custom elements enables you to create custom HTML tags which can exhibit any JavaScript behavior.

There are 3 rules that we need to follow while creating a custom element.

  1. You cannot register the same name for Custom Element tag more than once.
  2. Web Components tgs cannot be self closing.
  3. The name of a Web Component needs to contain a hypen (-).

Custom elements contain their own meaning, behaviors, markup and can be shared across frameworks and browsers.

Javascript Class:

Registering the component using define()

Adding the component to HMTL

CustomElementRegistry is the controller of custom elements and allows you to register a custom element on the page. Custom Element can be defined like:

where ‘my-component’ refers to the name of the element, MyComponent is a class object that defines the behavior of the element and optionally, an options object containing an extends property, which specifies the already built-in element, your custom element inherits. Here it extends the <p> element.

A custom element’s class object is written using standard ES class syntax. For example, MyComponents structured like so:

There are two types of custom elements:

  1. Autonomous custom elements
  2. Customized built-in elements

Autonomous custom elements

Autonomous custom elements nearly always extend HTMLElement.

 constructor() definition always starts by calling super() so that the correct prototype chain is established.

Inside the constructor, we have all the functionality the element will have when an instance of it is created. Here we attach a shadow root to the custom element, use some DOM manipulation to create the element’s internal shadow DOM structure which is then attached to the shadow root.

Then, we register our custom element on the CustomElementRegistry using the define().

It is now available to use .We use it in our HTML like:

Customized built-in elements

Customized built in element example — expanding-list. It is like the expanding/collapsing menu.

We define our element’s class:

The difference here is that cutom built in element is extending the HTMLUListElement interface, and not the HTMLElement. So it has all the characteristics of a <ul> element with the functionality we define built on top, rather than being a standalone element. This difference makes it a customized built-in element.

We register the element using the define() method, but here it also includes an options object that details what element our custom element inherits from:

Using the built-in element in a document  looks different:

You use a <ul> element as normal, but specify the name of the custom element inside the is attribute.

Using the lifecycle callbacks

We can define different callbacks methods inside a custom element’s class definition, which fire at different points in the element’s lifecycle:

  1. connectedCallback: Invoked each time the custom element is appended into a DOM element and each time the node is moved
  2. disconnectedCallback: Invoked each time the custom element is removed or disconnected from the DOM.
  3. adoptedCallback: Invoked every time the custom element is moved to a new document.
  4. attributeChangedCallback: Invoked each time one of the custom element’s attributes is modified like whether any attribute is added, removed, or changed. (Changed attribtes are specified in a static get observedAttributes method)
Example.

This is an example that simply generates a colored square of a fixed size on the page. The custom element looks like this:

In the constructor class, we attach a shadow DOM to the element, then attach empty <div> and <style> elements to the shadow root:

The important method in this example is updateStyle() . This function takes an element, gets its shadow root, finds its <style> element, and adds width, height, and background-color to the style.
The updates are all handled by the life cycle callbacks placed inside the class definition. The connectedCallback() runs each time the element is added to the DOM  and so here we run the updateStyle method to update the styles of the element.
The disconnectedCallback() and adoptedCallback() callbacks here just are used to inform us if an element is removed or moved to a different page.
The attributeChangedCallback() callback is run whenever one of the attributes is modified. We are running the updateStyle() function again to make sure that the square’s style is updated.

To get the attributeChangedCallback() callback method  to fire when an attribute changes, you have to observe the attributes. We get the array containing the names of the atrributes by specifying a static get observedAttributes() method inside custom element class.

Shadow DOM

The Shadow DOM API provides a way to attach a hidden separated DOM to an element and enables us to keep the markup structure, style, and behavior hidden and separate from the other code on the web page.

The Shadow DOM is just like other DOMs that browsers can generate from HTML code, but the difference between those is the way they are being generated and how they are being used and behave with other elements on a web page. The code inside a shadow DOM cannot affect anything outside it.

Shadow DOM allows hidden DOM trees that can be attached to elements in the regular DOM tree. The shadow DOM tree starts with a shadow root, which can be attached to any elements you want.

For Example:

The DOM Tree will look like this:

html
└─ head
│ └─ title
│ └─ Web Components ftw!
└─ body
└─ h1
└─ Let’s learn about Shadow DOM!
└─ button
└─ Click me

Below are the shadow DOM terminology, we must be aware of:

  1. Shadow host: The DOM node to which the shadow DOM is attached.
  2. Shadow tree: The DOM tree which is present inside the shadow DOM.
  3. Shadow boundary: The place where the shadow DOM ends, and the regular DOM begins.
  4. Shadow root: The root node of the shadow tree.

Usage

Shadow root can be attached to any element using the Element.attachShadow() method. Options object is the parameter that contains one option — mode — with a value of open or closed:

 Open means that we can access the shadow DOM using JavaScript written in the main page and for closed set, you won’t be able to access the shadow DOM from the outside.

If we want to attach a shadow DOM to a custom element, we would use something like this:

When shadow DOM is attached to an element, we can manipulate it using the same DOM APIs as we use for the regular DOM manipulation:

 Example:

Here we take an image icon and a text string, and embeds the icon into the page. When the focus is on the icon, it displays the text in a pop-up box. In a JS file, we define a class PopUpInfo, which extends HTMLElement:

 Then attach a shadow root to the custom element.
 After that we create a <style> element and populate it with some CSS to style it:
 The last step is to attach all the created elements to the shadow root:

// Define the new element

This is how we add it in HTML.

 

HTML Templates and Slots

<template> and <slot> are the elements, which are used to create a flexible template that can be used to generate the shadow DOM of a web component.

When we have to reuse the same markup on a web page, we can use a template rather than repeating the same structure over and over again in the HTML. This can be acheived using HTML <template> element. Template and its contents are not rendered in the DOM, but it can still be referenced using JavaScript.

Example:

 To output the template content , we have to create a reference with Javascript and then append it to the DOM.
 We can add it to our HTML document like:

Adding flexibility with slots

Using templates, we can only display text inside it. It is possible to display different text in each element instance using the <slot> element. Slots are identified by their name, and allows you to define placeholders in your template that can be filled with any markup.

To add a slot into our example, we could update our template’s element like this:

If the browser doesn’t support slots or if the slot’s content is not defined when the element is included in the markup, <my-paragraph> just contains the fallback content “My default text”.

To define the slot’s content, we include an HTML structure inside the <my-paragraph> element with a slot attribute whose value is equal to the name of the slot we want it to fill. For example:

 Below is an example to show how to use <slot> together with <template>,

Create a <element-details> element with named slots in its shadow root and design the <element-details> element in a way that, when used in documents, it is rendered from composing the element’s content together with content from its shadow root.

First, we use the <slot> element within a <template>, to create a new “element-details-template” document fragment containing some named slots:


That <template> element has several features like <style> element with a set of CSS styles and the <template> uses <slot> and its name attribute to make three named slots:

<slot name=”element-name”>
<slot name=”description”>
<slot name=”attributes”>

The <template> wraps the named slots in a <details> element. Creating a new <element-details> element from the <template>.

Next, create a new custom element <element-details> and use Element.attachShadow to attach to it.


Using the <element-details> custom element with named slots in our document,


The above code snippet has two instances of <element-details> and both use the slot attribute to reference the named slots “element-name” and “description” in the <element-details> shadow root .

The first <element-details> element references the “attributes” slot using a <dl> element with <dt> and <dd> children. The second <element-details> element doesn’t have any reference to the “attributes” named slot.

Add more CSS for the <dl>, <dt>, and <dd> elements in our doc:

Below is the Result.

Web components Vs React Components

There is a misunderstanding that we often think Web Components and React are the same. The truth is we can use them for a similar result, but their primary use is different.

Web Components

The goal behind using Web Components is to provide a safe environment to create components with readability, encapsulation, and isolation. Web Components uses three main technologies — Custom Elements, Shadow DOM, HTML Templates, but the important ones that most people use are Custom Elements and Shadow DOM.

React

React’s primary use is to provide UI components that can render as fast as possible. It’s primary focus is speed and performance. There are other important reasons to use React. It includes: better development experience, debugging, and it’s simple.

Conclusion

Both are excellent technologies, but focus should be on the main for what they were created. Here we see that encapsulation and performance are the areas where these two technologies diverge. We can use React inside Web Components to speed up the rendering, and use Web Components inside React to simplify and provide clean encapsulation and isolation of custom elements.

About The Author