tags:

Why I believe in web components

After years of hacking out React, Angular, JQuery and other popular javascript libraries, I realized that they all suffer from the lack of freedom. Whenever I need to break out from the set of rules, I need to start hacking out my way through the obstacles of the library limitations.

Angular is awesome, it's robust and provides 360 degrees of solutions for writing web applications. The community worked hard to enhance it and extend it to do any common task that was not bundled up in the core package.

But to provide a full blown solution for the general developer, you need to create conventions that some may not like, the learning curve is not helping out with that. Also, to robust the library, you have to make compromises. Such as performance and lifecycle speed.

Then came React, squeaking out that Angular is slow and it is a new robust solution that can replace Angular. Can it? It provides a very slim rendering solution, using a clever technique of manipulation a virtual DOM, forcing only the changes to render out to the real DOM. That's clever, like git does with code, React does with DOM. So it's super fast compared to Angular.

But React is no framework, and it's no solution for enterprise-scale web development. It just makes rendering less spaghetti. So let's use redux flux reflux and i'd say just flush it out the toilet because if you need so many tools to support a rendering helper, then you are obviously going the wrong way.

I won't even mention JQuery as it has always been a cool and useful tool to cut down code in order to do the job. And lightweight versions of JQuery are now bundled within plenty of popular libraries, from D3 to Angular itself.

And that is why I was still looking for the crown jewel. It must be out there.

Then came web components. It feels like the actual implementation of the existing controls and HTMLElements is now exposed and can be used by anyone. All web components have the same lifecycle, the same behavior and they are all based on the same conventions of any other existing DOM element.

And it has a code-behind.

If it sounds familiar, then you are correct.

dot-NET, JavaFX, windows visual studio, and my favorite of all, Adobe's Flex, all the awesome frameworks for building rich client applications. That was correct until the mobile and content bosses of the world made the hard demand to support mobile. Everything went to the trash.

Now web components bring the joy and happiness of the ONLY practice and convention that is good for building client applications, only it's supported within the sandbox of the browser. And all modern browsers support it. And you don't need any 3rd party library. None.

Extensibility

Web components can be easily extended. The natural web component lifecycle has 4 points:

  • createdCallback() once the element is first created
  • attachedCallback() once the element is attached to a DOM node
  • detachedCallback() once the element is removed from a DOM node
  • attributeChangedCallback() every time a node's attribute is modified

Beside all these lifecycle methods, it also has all the other HTML element methods, such as appendChild, insertBefore, normalize, etc...

Here's a little code block that examples how I can extend my component's capabilities.

class BaseElement extends HTMLElement {
    /** @override **/
    attachedCallback() {
        this.beforeAttach(arguments)
        super.attachedCallback(arguments)
        this.render(arguments)
        this.afterAttach(arguments)
    }

    /** @abstract **/
    beforeAttach() {}

    /** @abstract **/
    render() {}

    /** @abstract **/
    afterAttach() {} // abstract
}

And in my actual custom element:

document.registerElement('my-custom-element',
    class MyCustomElement extends BaseElement {

        validate() {
            /* a piece of code that validates values
               and make modifications in the properties */
        }

        beforeAttach() {
            this.validate()
        }

        afterAttach() {
            /* a piece of code that adds listeners,
               dispatches events or communicates with other
               nodes in the DOM */
        }

        render() {
            /* piece of code that writes the inner HTML,
               or add elements to the shadow-DOM */
        }
    })

Pretty simple, as now every component I write that extends my BaseElement has new capabilities of doing tasks before. Now i have a more robust lifecycle that can go through my whole project

  • createdCallback()
  • attachedCallback()
    • beforeAttach()
    • render()
    • afterAttach()

It's now much closer to the FLEX's or .NET's lifecycle with very little code to robust the project.

Data Binding

I have found a solution for simple property-level data binding by adding this step into the lifecycle:

  • createdCallback()
  • attachedCallback()
    • beforeAttach()
    • render()
    • createBindings()
    • afterAttach()

Assuming a bindable attribute would have the following pattern:

<my-custom-element>
    <my-custom-element-child custom-attribute="@{someProp}">
</my-custom-element>

I would like the custom-attribute on the custom child to be be changed whenever someProp on the my-custom-element is being modified. Thank you javascript for the option of __defineSetter__ method in every Object's prototype

createBindings() {
    for (let child of this.children) {
        for (let i = 0; i < child.attributes.length; i++) {
            try {
                // this should find someProp in @{someProp}
                let propName = /@{(.+)}/.exec(child.attributes[i].nodeValue)[1]
                let attributeName = child.attributes[i].nodeName
                this.__defineSetter__(prop, function(value) {
                    this.prop = value
                    child.setAttribute(attributeName, value)
                }
            }
            catch(err) {
                // sorry, but no cigar
            }
        }
    }
}

Simple huh?

But this naive solution doesn't work well when the content of your elements is dynamic and changes, nodes being removed and added again. The robust solution is to keep a private binding dictionary and store the setter methods in it, while adding custom bind attributes to the element with a unique hash. It's not that hard to do, as you have to do it only once, in the super class BaseElement.

You can find a working example in my work-in-progress slim-web-components library that can be found here: https://github.com/eavichay/slim-web-components

I'd appreciate people to fork and help me maintain this project as the goal is to create lifecycle, property bindings, method bindings and other good stuff that were native on FLEX framework, as long as the code is minimal and slim (as the name suggests)

The purpose of this project is NOT to provide controls and elements, but to provide the capability of writing rich web-component applications without any 3rd party library (except for JQuery-light for easy DOM tricks).

Please feel free to comment, fork, criticize.

Avichay Eyal avichay@tikalk.com


Avichay is a tech enthusiastic, full-stack developer and software architect focused on web development for (almost) any platform since 2007