Web Components Using Vanilla JS
If you are a developer for some time, you know the value of reusing elements that you have been created before. For example, every time you are defining properties of a circle with given radius and color, you may choose to write something like that
circle1.radius = 10;
circle1.color = 'red';
circle2.radius = 3;
circle2.color = 'blue';
But for the sake of clean code, creating a function would be more conventional.
circle1.update(10, ‘red');
circle2.update(3, ‘blue);
We can explain Web Components as the solution for the reusability of UI. You might be familiar with frameworks such as React, Angular etc. which allows you to create reusable components. When I first started to developing Front-end, I totally ignored the capabilities of Vanilla JavaScript and learned React and Angular like it was the only way to manage this. However, pure, old, good JavaScript supports this feature without requiring any other framework or library.
In this story, we will see how to create Web Components and attach them to the HTML using only JavaScript.
Starting With The Files
Firstly, I have to files index.html and index.js in my repository. We can start with standard HTML5 content, and attach our element later.
HTMLElement and Shadow DOM
In index.js file, we will define a class called “MyCircle” which will extend the “HTMLElement” class so that we will be able to represent our class in HTML document tree.
In constructor, we should create our Shadow DOM. Shadow DOM allows us tp attach a hidden, separate DOM to the element. We can add styles so that it cannot leak outside. This means that I can have two different element with the same class name however they will not effect each other if the styling is added to the shadow. This helps us to avoid conflicts among components. {mode: ‘open’} states that Shadow DOM is accessible using JS in our component.
We will add our render function to create the HTML view. With shadow, we are able to add innerHTML and define it using `` quotes. InnerHTML might not be the best solution here, but since we are avoiding using any libraries such as Lit, innerHTML is satisfactory for us.
Life Cycle Callbacks
Now, it is a good time to mention “life cycle callbacks” in the web components. There are four types of life cycle callbacks in HTMLElement web component. This methods are automatically called when custom element’s behavior is affected. There are four types of callback functions defined.
- connectedCallback: When the custom element first attached to the document’s DOM, this function is invoked.
- attributeChangedCallback: We will see how to define attributes to our custom element, later. This method is triggered when the values of the attributes are updated.
- adoptedCallback: This will be called if the custom element is moved to a different document.
- disconnectedCallback: Invoked when the custom element is removed or disconnected from the document’s DOM.
Back to our project, we want to include connectedCallback because when our custom element is connected to the document, we would like to render it. We need to add our render() function in connectedCallback.
Defining the Custom Element as an HTML Tag
We need to access our custom element from the html file. Like <button>, we need a way to attach a tag. customElements.define() is a function to do that. First parameter will be the tag and the second element will be our custom element class that we are rendering.
I also added the initial styling to the innerHTML. When we have the attributes, we will be changing those values.
Now we will be table to use<my-circle> tag in our index.html. We will also add our index.js as script to add the custom element.
Adding More To The Custom Element
Now, i will two inputs to my custom element to trigger an event. The first input element will take a positive number to pass radius value and the second input will determine the color of my circle. For now, we don’t care about styling. Our render function is now:
Setting Attributes to the HTML Tag
HTML Elements may have attributes. For example, <input> tag has a attribute called “type” so if we give it a value of “number” it does not allow us to enter any text in the input element.
For <my-circle>, we will have two attributes “radius” and “color”.
To make these attributes valid, we need to define them as properties in our MyCircle class.
Firstly, we need add tell our class that which attributes this tag can have. It’s done with a static get methods called observedAttribute. After that we will attach attributeChangedCallback life cycle hook to invoke when the attributes our changed (it will be done when the inputs are changed). We want this methods to re-render our shadow html view. To use this hook, we also need to define get and set methods for our custom attributes. See how I used these properties in style of the MyCircle.
Handling Events
Now, all I need to do is that connecting color and radius attributes with corresponding input values and attach a custom event to them.
Firstly, I need to pass the initial values to the inputs. Then I will create the functions to listen when the input values are changed. We need to add these functions to connectedCallback to attach when the DOM is created and to the attributeChangedCallback to re-attach the event listeners when the attributes are changed.
And this is the our final index. “setAttribute” methods invoke attributeChangedCallback method so that we render the component to see the result. Let’s see the final product:
This is it!