Home

Native Web Components - part I

Aug 13, 2022

Content

What is a native web component?

MDN defines a web component as follow: "Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps."
We use the term native here in order to specify that we won't use any third-party library.

Why use a native web component?

For desktop applications, it has been common practice for a very long time to encapsulate complex user interface elements in self-defined components. Of course, this is often just a collection of the known things plus code, combined under one bonnet, and you can also write it all down individually. But it doesn't necessarily become clearer in the end. It is an advantage if the complexity of the component disappears behind a single line of code, at least at the point where it is integrated into the rest of the context.

Components can also be used in the web environment, if the use case does not yet loudly call for a web application (i.e. what one would have created on the desktop as a programme with GUI). For example, data should be displayed in the form of a pie chart. You can do this by taking a canvas and painting something on it, or by taking an SVG and inserting a few declarations for the geometric shapes, or perhaps by using CSS to conjure up pie pieces from a few rectangles and arranging them in a circle. Or you create a widget from it, to which you just pass the data.

If you repeatedly attach Javascript to one of the HTML elements, or even if several HTML elements always form a recurring unit in the same arrangement plus Javascript code, then you can think about creating a component out of it. This is then easier to integrate than the complex HTML code plus event handler wiring, which remains invisible to the user in the Web Component in the Shadow DOM. Since the Shadow DOM is encapsulated in itself, no consideration needs to be given to any existing IDs or CSS rule sets.

Components are therefore very strong in giving a semantic framework to reusable things.

Benefits of native web components:

Native means that no third-party JavaScript libraries are used, so you don’t need to plug in additional libraries to use a web component. Since it's an HTML standard, and therefore, it's flawlessly supported by all modern browsers, whereas for example React components can only be used in a React environment.

Easy to share and reuse Web components are very easy to share and make the handling of multiple projects or ecosystems with different technology stacks possible.

Simple to integrate good approachable for specific tasks. You might opt for the Web Components if there is a specific reason. For example, it applies to the cases when the project requires a high level of security so you shouldn’t use third-party libraries, you must use native technology and you must control the whole content of imported libraries.

HTML is affordable from other tools. Web Components are especially useful if you plan to use HTML from other non-web-native programming languages, such as Java or Kotlin.

How to implement a native web component?

A simple use of a web component could look like that:

class MyComponent extends HTMLElement {...} window.customElements.define('my-component', MyComponent);

and in HTML:

<my-component></my-component>

But in order to know how to implement a web component there are a several of APIs that we should consider before digging in the implementation details.

Custom Elements

With Custom Elements, you can "invent" new HTML tags, update existing HTML tags, or extend web components done by others. The API is the foundation of web components. It brings a web standards-based way to create reusable components using nothing more than vanilla JS/HTML/CSS. The result is less code, modular and more reusable.

Custom elements follow some rules so that parsers can distinguish them from regular HTML elements:

  • Names of custom elements must always contain a hyphen (kebab-case). Examples: <my-comp>, <my-element>. This also ensures forward compatibility of any new tags. (Google tracks the use of custom elements and plans to incorporate "successful" elements into the standard).
  • A tag can only be registered once, otherwise there will be a DOMException error.
  • Custom elements must always be closed with a closing tag. Example: <my-comp>...</my-comp>

Shadow DOM

Shadow DOM provides a way for an element to own, render, and style a chunk of DOM that's separate from the rest of the page. Heck, you could even hide away an entire app within a single tag.

With Web Components, you can design your own reusable components in the shadow DOM whose HTML elements are encapsulated and whose CSS is only applied in this scope. These are hooked into the normal DOM of the website and are not directly visible and accessible to debuggers and scripts.

The approach behind the shadow DOM has long been used for more complex HTML elements. For example, sliders, the details element with its summary marker or players such as audio and video consist of only one element, which is, however, composed of several "hidden" elements.

Some terminology:

  • Shadow Host: the DOM element that hosts the element tree of the shadow DOM as an insertion point.
  • Shadow Root: Root element of the subtree of the shadow DOM. This is a node that represents the boundary between the "normal" and "hidden" DOM. This boundary encapsulates the nodes of the shadow DOM from scripts and CSS rules of the website.
  • Shadow DOM: "hidden" and encapsulated subtree of the element tree that is not accessible to other scripts and CSS rules.
  • Shadow Boundary: Boundary of the "hidden" DOM.
  • Light DOM:: the normal DOM.

The shadow DOM can be created and manipulated just like the "normal" DOM with the DOM methods.

attachShadow:

The method attachShadow requires an object as a parameter and expects a mode property in it. mode can have the value "open" or "closed" and describes whether the shadow DOM is accessible via the shadowRoot property of the element ("open") or not ("closed"). We will go through these two modes later in the examples.

HTML Templates

The template element makes it possible to specify templates as content fragments in the HTML document, which are parsed for correctness but not rendered. They can they be appended to the element tree only when they are needed , using appendChild().

They are a good alternative to time-consuming content reloading with Ajax or dynamic creation with createElement, which quickly becomes inconvenient and confusing with more complex HTML structures.

<template id='myTemp'> <h1>Headline</h1> <p>Paragraph</p> </template>

Templates and their contents:

  • are inactive until they are called up. The markup is not visible and is not rendered. Elements of the template cannot be accessed with document.getElementById() or querySelector() either. However, you can access the content indirectly via the content property and childNodes with console.dir(document.getElementById("newRow").content.childNodes).
  • do not affect page load time, as scripts, images and other media files are not loaded or played until the template is used.
  • can be written anywhere in the head, body or frameset and can have any content (e.g. also the style element). template can also be a descendant element of table or select, which otherwise only have defined child elements.

How to check if the browser supports templates?:

function supportsTemplate() { return 'content' in document.createElement('template'); }

It is possible to nest templates within each other as desired.

<template id="myTemplate"> <h1>Headline</h1> <template id="myNestedTemplate"> <h1>Nested Headline</h1> <p>text</p> </template> </template>

Later in the examples we'll see what advantages do template elements have.

Lifecycle Callbacks

You can define several different callbacks inside a custom element's constructor, which fire at different points in the element's lifecycle:

let's have a look at the following example:

const template = `<div>content of my custom element</div>`; window.customElements.define( 'my-component', class extends HTMLElement { constructor() { super(); } connectedCallback() { } disconnectedCallback() { } adoptedCallback() { } attributeChangedCallback() { } } )
  • constructor: A constructor can be used for creating an instance of the Shadow Dom, setting up event listeners and for intializing a component’s state, but it’s not recommended to execute tasks like rendering or fetching resources here. That should be deferred to the connectedCallback function.

Defining a constructor when creating ES6 classes is actually optional. When undefined, the JavaScript engine will just initiate an empty constructor, but its actually mandatory when creating Custom Elements. This is because a Custom Element is custom 😵‍💫 and the prototype and constructor are defined by the user.

The first method called in the constructor is super, a keyword used to access and call functions on the parent object. super must be called first in order to access this and establish the correct prototype chain. In this case, the parent element being accessed is HTMLElement, a class that provides a standard set of properties, event handlers, methods, and events.

constructor() { super(); ... }
  • connectedCallback: Will be called once each time the web component is attached to the DOM. Since the shadow root was attached in the constructor(), then connectedCallback() can be used to access attributes, child elements, or attach event listeners. If the component is moved or removed and re-attached to the DOM, then connectedCallback() will be called again.
connectedCallback() { console.log('connected'); }

The connectedCallback can be triggered more than once during its lifetime! So be aware that code that needs to be executed only once is guarded.

  • disconnectedCallback: Invoked when the custom element is disconnected from the document's DOM. This lifecycle hook is triggered when the element is removed from the DOM and is the ideal place to add cleanup logic (the code that needs to be executed before the element is destroyed) and to free up resources. We can use this callback to:
  • notify another part of an application that the element is being removed from the DOM
  • free resources that won’t be garbage collected automatically:
  • unsubscribe from DOM events
  • stop interval timers
  • unregister all registered callbacks with global or application services (i.e. event handlers)
disconnectedCallback() { console.log('disconnected from the DOM'); }

If you don’t clean up your code properly, you might risk memory leakage (memory that the application no longer needs, but has not been returned to the operating system or memory pool).

Note that this callback is never called when the user closes the tab! Note that this hook can be triggered more than once during its lifetime!

  • adoptedCallback: Invoked when the custom element is moved to a new document.

An element can be adopted into a new document (i.e. someone called document.adoptNode(element)) and has a very specific use case. In general, this will only occur when dealing with <iframe/> elements where each iframe has its own DOM, but when it happens the adoptedCallback lifecycle hook is triggered. We can use it to interact with the owner document, the main document or other elements.

adoptedCallback() { }

Note that the element is not destroyed and created again while adopting, so the constructor() method won’t be called.

  • attributeChangedCallback: Invoked when one of the custom element's attributes is added, removed, or changed. this method is fired only if a list of the custom element attributes is returned in observedAttributes().

Example

Now if we put everything into a small project, where we'll try to create a popup as a web component:

Conclusion

Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.

Next

int the next part (II) we will have a look at HTML templates, slots, and scoped slots. With a practical example we'll try to handle different use cases.

Made with ❤️ by Marouen Mhiri