The development of Web Application has led us to rewrite some pieces of User Interface so many times in different varieties of JavaScript frameworks for compatibility and interoperability.
When we build our User Interface, we have to think about performance, accessibility, compatibility, reusability, extensibility and maintainability. In my personal opinion, many of these aspects are the same challenges we have in any other “classical” application. Object Oriented Programming is a paradigm mostly used to build enterprise applications, it uses concepts such as encapsulation, inheritance, interoperability to gain robust, reusable portions of code called Classes. Then we have SOLID Principles such as Inversion of Control, Single Responsibility and finally, the best one, the simplest principle in programming: DRY (Don’t Repeat Yourself).
In the Web Development world, to achieve these important goals, we need to introduce a new idea, called “Component”. It’s not a new concept and it has been around for a long time. I can think about Component like a Class in OOP.
One of the first frameworks that introduced the concept of components was AngularJS (do not confuse with Angular, they are very different).
Today we have a lot of frameworks and libraries, like Angular, React or VueJS, which are trying to solve the problem of “Thinking in Web Components”; they continue to use the idea of Component, but they abstract from the DOM (Document Object Model). They give us the power to define our custom HTML tag in order to enrich the “grammar” of the HTML language. We are able to define our custom behaviour, with some JavaScript code, attached to these new elements.
One of the most powerful features of Angular is to be able to define a new syntax constructor by using the decorator @Component
. It allows developers to decorate a class with some metadata in order to define the custom tag HTML with template (View) and a class, which defines the behaviour of the component itself. In addition, we can take advantage of the Shadow DOM to provide complete encapsulation for our components.
All elements and styles that belong to an HTML document are defined in one global scope. A Shadow DOM is a “DOM within a DOM”, where we can build an isolated DOM tree with our own elements and styles.
These frameworks have their own definition and implementation of what a component is, but they lack a common API. They share only the idea about what a component should be, but you cannot share a component with different technology stacks or, at least, it is not a quick job. They also implement a custom DOM like Virtual DOM in React and VueJS or “Emulated Native Scoping” in Angular to emulate Shadow DOM.
In “Get hyper-excited for web standard”, Jiayi Hu’s talk at Codemotion Rome 2019, we saw what a Web Component is in a “native” way using standard API, that tries to resolve this fragmentation caused by the presence of a non-standard way of building reusable piece of code.
Web Component is a set of different APIs that allow us to create highly reusable UI components that will work across modern browsers and they can be used with third party frameworks and libraries. They allow us to define reusable custom elements via standard specifications, such as Custom Element and Shadow DOM.
Custom Elements
The Custom Elements API provides a way for programmers to define our own custom HTML tags, with fully-featured DOM elements with attached behaviour on them. With Custom Elements API, we can instruct the browser parser to properly construct an element and how elements should react to the user events.
To define our custom element, we can use some of the newer ES6 JavaScript features such as Classes. We need to extend HTMLElement base class and call the super()
method inside the constructor. Then we can have one method of the lifecycle of a component: connectedCallback
, but it is not mandatory to have one. This is a particular method that it is invoked when our component is created and added to the DOM. Inside this method we will do initialisation logic, data fetching or something else that can occur in the beginning.
In the connectedCallback()
method we can set the textContent
of our element within a timer function like setInterval()
, but we can have complete access to the DOM tree, such as querySelector
and other methods to get access to the DOM. Because of the use of a Timer, we need to write the logic to release it inside the disconnectedCallback()
, another life-cycle method of the Custom Element API, called when the element is detached from the DOM tree.
Finally, we need to register our component to the DOM with the define()
. We need to make sure we define the name of our custom element with at least one dash in the name, in order to prevent collision with native elements that have no dashes in the name. Here is an example:
Jiayi continues his talk showing us an implementation of a custom element with React. You can find the code on Codepen.
Life-cycle Methods
We already covered a couple of life-cycle methods in the previous example.
Beside connectedCallback()
and disconnectedCallback()
there are other life-cycle hooks we can use.
connectedCallback
: is called when our element is added to the document DOM.disconnectedCallback
: is executed when our element is removed from the DOM. You can add some logic to remove listeners or do something else such as disconnecting from Web Sockets.adoptedCallback
: is called when you move the element to a new document.attributeChangedCallback
: this is useful whenever attributes of our element have changed.
HyperHTML and HyperHTMLElement
HyperHTML is a very small library (4kB) made by Andrea Giammarchi. To get started, all you will need to know is HTML and, of course, a good knowledge of JavaScript.
HyperHTML is a DOM & ECMAScript standard compliant, zero-dependency, fully cross-platform library suitable for declarative and reactive Web Applications.
The core features are built on top of Template Literals, they allow us to embed an expression in a string and we can use multi-line strings and interpolation. They were called “template strings” in previous edition of ES6 specifications.
You can try this snippet of code inside Stackblitz, but remember to import the hyperHTML dependencies:
The bind
function declares the layout inside an existing DOM element. It renders the template inside a DOM element. It returns a function that helps us to mounts the nodes inside the binded element the first time, whereas successive changes in the interpolated values will update only the modified nodes.
HyperHTMLElement is based on top of hyperHTML and it’s part of its ecosystem. It helps to define our Custom Elements by leveraging hyperHTML as a template engine instead of imperative vanilla JS.
Template literals are enclosed by the back-tick (` `
) character instead of double or single quotes; they can contain placeholders. The dollar sign and curly braces (${expression}) indicate the presence of a placeholder. You can find a quick example below:
contain a list of static chunks between interpolations: [‘1’,’ 3’]
. The second argument is a rest operators argument that will contain the dynamic interpolated values: [‘2’,’4’]
.
HyperHTML uses the Levenshtein distance algorithm to update the internal list of items or nodes. Thanks to this, we have an efficient “updating DOM” algorithm because it needs the smallest amount of DOM operations to update to the latest template.
Finally, ViperHTML is another library from Andrea Giammarchi where we can write Web Components for “Server Side Rendering”.
In conclusion, there are many interests around Web Component. Companies such as Google, Tencent and GitHub are going to use the new Web API standard for some of our projects. Of course, Web Component will not replace our mode to write an application and you can happily continue to use Angular or other frameworks.
Standards are the best way, if not the only one, to move the Web forward (by Andrea Giammarchi). Happy hacking!