Create Custom HTML Element With customElements.define()
Tags: javascript, webdev, todayilearned, html
Published: 2021-10-21 12:59:16

So, i developed angular application about a month ago, and i wondered why in angular app the element name is unique and different with HTML element, and the element will still unique even in build release.

Today when i read _Javascript Notes For Professionals_ (it is a book writen by GoalKicker group, you can download it [here](https://books.goalkicker.com/JavaScriptBook/)), i saw something that took my interest, "Chapter 51: Custom Elements". 

That chapter explains about creating custom element with `document.registerElement()` which is already deprecated. So, i searched for another method, and here is what i found.

## 1. What We Will Build
A simple timer element named `app-timer` with red background, of course we will build it as custom element.

## 2. Code
We will use `customElements.define()` method in this post, because it's the recommended way to define custom element in modern javascript.
Create an index.html file and paste these codes:
```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Custom Element Example</title>
</head>

<body>
    <script>
        class Timer extends HTMLElement {
            constructor() {
                super()
            }
        }
        customElements.define('app-timer', Timer)
    </script>
    <app-timer/>
</body>

</html>
```
Okay let's explain it one by one

First, we create `Timer` class that extends `HTMLElement` class, then we define the constructor method with nothing inside it except executing `super` method.

Then, we call define method from `customElements` class with "app-timer" and `Timer` class as its argument, "app-timer" is element tag name (like body, footer, head) and `Timer` class is element handler class.

Please note that element tag name **should include a hypen(-)**, "x-footer" and "my-element" is a valid element tag name while "myblock" and "header_block" is not.

Then let's run this html file in browser.
![Nothing here](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v2vtx1c03yl1d0m3holw.png)
 
Yep, there's nothing here, because we haven't add anything into the custom element, it would be scary if there is something shown in the page.

Add timer functionality to constructor method, so it will looks like this:
```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Custom Element Example</title>
</head>

<body>
    <script>
        class Timer extends HTMLElement {
            constructor() {
                super()
                const shadow = this.attachShadow({mode:"open"})
                shadow.innerHTML = "1"
                setInterval(()=>{
                    shadow.innerHTML = parseInt(shadow.innerHTML) + 1
                }, 1000)
            }
        }
        customElements.define('app-timer', Timer)
    </script>
    <app-timer/>
</body>

</html>
```
And again, let's explain things line by line

```javascript
const shadow = this.attachShadow({mode:"open"})
```
we define shadow constant with value returned from `attachShadow()` method. `attachShadow()` used to get our custom element DOM property, we can't access the property directly, it will throws error.

```javascript
shadow.innerHTML = "1"
```
You must be familiar with this code, yep, we add text to element's innerHTML.

```javascript
setInterval(()=>{
    shadow.innerHTML = parseInt(shadow.innerHTML) + 1
}, 1000)
```

Increase value of custom element's innerHTML by 1 every second.

Ok try to run it and you won't see blank page anymore.
![Basic Timer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gf1y3g4m7qml4t4s7lmh.png)
 
And that's it, we have added timer functionality to custom element.

### Is it enough? No

Don't forget about the red background part, we must add style to this custom element. We will place the code that change element style in `connectedCallback()` method. This method executed after the Element is rendered, so we won't face DOM issues.

```javascript
class Timer extends HTMLElement {
    constructor() {
        super()
        const shadow = this.attachShadow({mode:"open"})
        shadow.innerHTML = "1"
        setInterval(()=>{
            shadow.innerHTML = parseInt(shadow.innerHTML) + 1
        }, 1000)
    }
    connectedCallback(){
        this.style.backgroundColor = "red"
        this.style.padding = "10px"

    }
}
customElements.define('app-timer', Timer)
```

You can do anything in `connectedCallback()` method, manipulating DOM, rendering, there is no restriction. Ok, time to test our app again.
![Finished](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbbco3y0wp6m2k7hoxwe.png)

And as you can see, the `app-timer` element background color turns into red, i even added padding for beautifier. You can try it by your own on the codesandbox below.

{% codesandbox modest-fog-t72op %}

## References
- _Javascript Notes for Professionals_ 
- Google Developers Custom Element Docs
- Some StackOverflow Thread

Thanks to Tranmautritam in Pexels for their beautiful picture (the picture i used in banner).