days
-3
-6
hours
0
-8
minutes
-4
-5
seconds
-3
-9
search
Reaping the benefits

Web Components: Bridging the gap between frameworks

Sven Kölpin
API
© Shutterstock / Chaliya  

Components on the web have always been very framework-specific. That’s why it is not possible to use a component written in Angular directly in React (or vice versa). This is exactly where Web Components can help!

Components can be used to assemble user interfaces from configurable, reusable building blocks (“widgets”). Libraries such as jQuery / jQuery-UI once laid the foundation for creating custom components on the web. These libraries allow the transformation of conventional HTML elements into rich components with a custom appearance, behavior, and state. Nowadays, the development of web applications without the use of component-based frameworks, such as Angular or React, is hard to imagine.

Components on the web have always been very framework-specific. The APIs, and even the definition of what actually makes a component differ among the frameworks. That’s why it is not possible to use a component written in Angular directly in React (or vice versa) and even basic widgets often have to be redeveloped for different web applications.

This is exactly where Web Components can help.

    iJS React Cheat Sheet

    Free: React Cheat Sheet

    You want to improve your knowledge in React or just need some kind of memory aid? We have the right thing for you: the iJS React Cheat Sheet (written by Joel Lord). Now you will always know how to React!

The Web Components standard

The Web Components standard consists of various APIs which, when combined, enable the creation of powerful UI components for the web platform. The specification creates a uniform and therefore the framework-independent definition of web-based components and lays the foundation for reusable UI widgets on the web.

Web Components are actually not very new. An implementation of the standard has been around in Google’s Browser “Chrome” since 2014. But other browser manufacturers have largely neglected early versions of the specification so that for a long time Web Components could only be used across platforms using polyfills and frameworks such as Polymer. Luckily the standard, which is now in version V1, has been modernized and is currently supported by most major browsers (see figure 1).

Figure 1

The Web Component specification is based on four standards: Custom Elements, HTML Templates, Shadow DOM and ES Modules. Each of these individual APIs is addressed via JavaScript. All specifications are standalone and can be used independently. This means a Web Component can be created by using all defined APIs or by just combining a few specific ones. In order to use the full potential of the specification, the usage of all APIs is recommended in most cases.

Custom Elements

The HTML standard has always provided components for the creation of user interfaces. These, mostly primitive elements (e.g. buttons, input fields or date fields), are not sufficient for most applications. With the Custom Elements API, it is finally possible to define custom HTML elements (“tags”) with a specific appearance and behavior in a standardized way. The Custom Elements specification forms the basis for the creation of Web Components.

Custom Elements must inherit from the HTMLElement, which is the basis for all components defined in the HTML standard. The class provides access to basic properties and methods of an HTML element (e.g., addEventListener, style, …). Currently, a Custom Element cannot derive from other native components (e.g. extends HTMLButtonElement). There is a separate specification proposal called “customized built-in elements” for this, but it has so far only been implemented in Chrome and is rejected by Safari.

The registration of custom tags is done via the custom Elements.define-interface. There are a couple of rules that need to be followed when creating new tags. Firstly, the name of each created component must contain at least one dash. This way, conflicts with native HTML tags can be avoided. Plus, all Web Components can easily be recognized by the name of the tag. Secondly, the double registration of an element name within the same page is not possible and leads to an error. Listing 1 shows the definition of a simple component.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Chapter: 01</title>
</head>
<body>
<!-- usage of the web component -->
<hello-component name="r10"></hello-component>

<script>
    class HelloComponent extends HTMLElement {
        static get observedAttributes() {
            //observe the name attribute.
            return ['name'];
        }

        constructor() {
            //called for every hello-component tag.
            super();
        }

        connectedCallback() {
            //called when the component is attached to the dom.
            this.render();
        }

        disconnectedCallback() {
           //called when component gets removed from the dom.
        }

        attributeChangedCallback(name, oldVal, newVal) {
            //called initially and whenever the name attribute changes.
            if (name === 'name' && oldVal && oldVal !== newVal) {
                this.render();
            }
        }

        render() {
            const name = this.getAttribute('name');
            this.innerHTML = `
                <h1>Hello ${name}.</h1>
            `;
        }
    }

    //register the hello-component
    customElements.define('hello-component', HelloComponent);

    //simulate attribute change after 1 second.
    setTimeout(() => {
        document.querySelector('hello-component').setAttribute('name', 'r20');
    }, 1000);
</script>
</body>
</html>

In addition to the constructor, which is called for every instance of an element, a Custom Element has different lifecycle methods (The adoptedCallback will not be considered in this article): the connectedCallback, disconnectedCallback, and attributeChangedCallback. The connected and disconnected methods are called when the respective component is inserted into or deleted from the DOM tree. They can be used to initiate rendering, to retrieve data from the server, or to register/release event listeners (e.g. Websocket connections). The attributeChangedCallback is invoked when any attributes that are marked as observable have changed. This callback responds to status changes or changes of a component’s configuration. As with all HTML elements, attributes can only be passed as string-based values. To pass complex JavaScript objects or functions into a custom element, properties or methods need to be used.

The Custom Element specification is already very powerful on its own and it provides much more interesting APIs. For an in-depth introduction, feel free to refer to this very well written Google Developer blog post.

SEE ALSO: Web components & micro apps: Angular, React & Vue peacefully united?

HTML Templates

The HTML Template API allows creating reusable HTML fragments that can be used to render Custom Elements. In contrast to what the name implies, there is no real browser-native templating engine included in the specification. For now, HTML templates are especially useful for enabling performant rendering of Custom Elements: Browser engines are able to efficiently process the template fragments produced by the HTML Templates. The API, therefore, provides a powerful alternative to the string-based rendering approach (used in Listing 1). A template can contain anything that can be passed via the innerHTML property, including <style /> tags or <link /> elements. Listing 2 shows an example of how HTML templates are used in Custom Elements.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Chapter: 01</title>
</head>
<body>
<hello-component name="r10"></hello-component>

<script>
    //definition of a reusable template.
    const template = document.createElement('template');
    template.innerHTML = `
       <style>
            p {
                background: rebeccapurple;
                color: white;
            }
        </style>
        <p>Hello to <span></span></p>
    `;

    class HelloComponent extends HTMLElement {
        connectedCallback() {
            //clone the template’s content
            this.appendChild(template.content.cloneNode(true));
            //set the name.
            this.querySelector("p span").innerHTML = this.getAttribute('name');
        }
    }

    //register the hello-component
    customElements.define('hello-component', HelloComponent);
</script>
</body>
</html>

Shadow DOM

The Shadow DOM (SD) specification provides an API for the creation of isolated and reusable components. The API allows constructing separate DOM structures that cannot be accessed from outside of the shadow’s host. Especially when styling components, the Shadow DOM offers many advantages. All CSS rules defined within the SD only apply inside of the component. In addition, external rules are only used if they are not explicitly overwritten in the shadow DOM. Furthermore, all IDs or CSS class names assigned in the shadow tree are only valid there. This makes the definition of CSS rules and the selection of DOM elements very simple and efficient. With the help of CSS properties, certain areas of the Shadow DOM can be configured externally, which makes it possible to create isolated yet reusable Web Components. Listing 3 shows an example of the Shadow DOM API. Another useful feature of the Shadow DOM is the <slot /> API, which allows passing entire DOM structures or Web Components into the SD. Each <slot /> can be given a specific role, which enables the composition of different Web Components (Listing 4).

<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Chapter: 01</title>
</head>
<body>
<hello-component name="r10"></hello-component>

<script>
    const template = document.createElement('template');
    template.innerHTML = `
       <style>
            /* the style will only be used within the shadow dom */
            p {
                background: rebeccapurple;
                color: white;
            }
        </style>
        <p>Hello to <span id="content"></span></p>
    `;

    class HelloComponent extends HTMLElement {
        constructor() {
            super();
            //create shadow dom (open mode).
            this.attachShadow({mode: 'open'})
        }

        connectedCallback() {
            //clone template into shadow dom.
            this.shadowRoot.appendChild(template.content.cloneNode(true));
            //id's can be used without causing conflicts
            this.shadowRoot
                .querySelector('#content')
                .innerText = this.getAttribute('name');
        }
    }

    //register the hello-component
    customElements.define('hello-component', HelloComponent);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Chapter: 01</title>
</head>
<body>
<hello-component>
    <!-- add child element as slot -->
    <h1 slot="title">R10</h1>
</hello-component>

<script>
    const template = document.createElement('template');
    template.innerHTML = `
        <p>Hello to <slot name="title"></slot></p>
    `;

    class HelloComponent extends HTMLElement {
        constructor() {
            super();
            this.attachShadow({mode: 'open'})
        }

        connectedCallback() {
            //clone template into shadow dom. Slot will be rendered automatically.
            this.shadowRoot.appendChild(template.content.cloneNode(true));
        }
    }

    //register the hello-component
    customElements.define('hello-component', HelloComponent);
</script>
</body>
</html>

It is important to mention that the Shadow DOM specification is, by no means, a security feature. The contents of the hidden DOM can still be examined by using the development tools of a browser. Programmatic access to the Shadow DOM of a component is also possible, at least in open mode.

ES Modules

The ES Modules (ECMAScript Modules) standard was not developed as part of the Web Component specification. The API belongs to the ECMAScript 2015 specification which offers the ability to define JavaScript modules that allow (dynamic) importing and exporting of functions and data structures from within JavaScript files. ES Modules are natively supported in all major browsers. For Web Components, the standard provides the basis for assembling user interfaces from different files.

Use-cases for web components

The primary use case for Web Components is to create framework-independent UI components. Thanks to the broad browser support, this is already possible today. The integration of native Web Components into single-page application (SPA) frameworks is very promising. In both Angular and Vue.js, Web Components can already be used without restrictions. The support in React is currently still in need of improvement. A detailed summary of the current status of the Web Component support in various frameworks can be found on this page.

It is possible to develop entire web applications exclusively on the basis of Web Components, without using any external libraries and SPA frameworks at all. This framework-less approach is, at least in theory, quite attractive. You no longer have to tie yourself to any third-party providers – all that’s needed is an official standard. Unfortunately, the APIs of the Web Component specification are relatively rudimentary. This means that in comparison to most of the common SPA frameworks, more code needs to be written to achieve the same thing. Lightweight libraries such as lit-html can help.

Conclusion

Web Components are a widely supported technology. The standard helps to avoid typical problems when developing web-based applications, such as the cross-framework reusability of components. Of course, this does not mean that any parts of an application are automatically interchangeable. The specification is mostly suitable for implementing technically driven components (e.g., special button elements or modal dialogs).

The Custom Element API is very rudimentary, so it might be helpful to use lightweight external frameworks or libraries when developing your own Web Components. For example, Stencil or skatejs offer promising approaches. These libraries help to create native and therefore interchangeable Web Components without having to use the, frankly said, cumbersome APIs directly.

Web Components are mainly driven by Google, which owns one of the most popular browsers on the market. So it’s definitely worth to take a closer look at the specification.

Stay tuned.

Author
API

Sven Kölpin

Sven Kölpin is an Enterprise Developer, Speaker and Author at open knowledge GmbH in Oldenburg. His focus and passion is the conception and development of web applications.


Leave a Reply

Be the First to Comment!

avatar
400
  Subscribe  
Notify of