Custom Elements
Tags: customelements, javascript, html
Published: 2018-09-26 12:30:25

---
title: Custom Elements
published: true
description: An introduction to native Custom Elements
tags: Custom Elements, javascript, html
series: An introduction to
---

## Introduction

Custom Elements, a subset of Web Components, are one of the coolest things to hit the web in my opinion. Effectively, they allow us to build out components that are native to the web platform, instead of using intermediary libraries or frameworks like React, Angular, Vue, or otherwise.

In this post, I hope to show you what a Custom Element is, how one is built and how to put it on the page.

## The foundations of a web component

All Custom Elements will share some common methods, an example of these can be seen in the code example below:

```javascript
class MyComponent extends HTMLElement {
  static get observedAttributes() {
    return [];
  }

  constructor(...args) {
    super(...args);
  }

  connectedCallback() {}

  disconnectedCallback() {}

  adoptedCallback() {}

  attributeChangedCallback(attrName, oldVal, newVal) {}
}

window.customElements.define('my-component', MyComponent);
```

Let's break down what is actually happening here.

### constructor()

> A constructor must always be ideally declared and any parameters passed to the parent also.

The constructor is always ideally where any event listeners, etc would normally be implemented, for example:

```javascript
...
constructor(...args) {
    super(...args);
    this.addEventListener('click', this.handleClick);
}

handleClick(event) {}
...
```

### connectedCallback()

> Called every time the element is inserted into the DOM.

Everytime the component is added anywhere in the page, at any time, it will fire this function.

### disconnectedCallback()

> Called every time the element is removed from the DOM.

If for example we delete the node or a parent node in the DOM tree, this function will fire since inherently it will remove the element from the aforementioned tree. 

The `disconnectedCallback()` will also run when the element is adopted elsewhere on the document or in a new page.

### adoptedCallback()

> Invoked each time the custom element is moved to a new document.

If the custom element is moved to a new page or document, this callback will fire.

### attributeChangedCallback(attrName, oldVal, newVal)

> The behaviour occurs when an attribute of the element is added, removed, updated, or replaced.

This function is fired whenever an attribute on the component is changed HOWEVER, only if the attribute that changed is currently being observed, which brings us to the `observedAttributes()`.

### observedAttributes()

> Attributes of the custom element we actually want to observe changes upon

As you can see, this method is declared as `static get observedAttributes()` and this clearly differs from other method declarations, this is because we want it inherited by any sub classes/components and we want to declare it only once to reference, heed, it is `static` (set for all inheritors and itself) and `get`table (for reference).

This function should return an array of strings where each string is the name of the attribute you wish to observe, for example:

```javascript
...
static get observedAttributes() {
    return ['id', 'my-custom-attribute', 'data-something', 'disabled'];
}
...
```

There are some other functions in the Custom Elements specification but these are the ones we would primarily use day to day.

## A basic component

Let's build a basic component that says hello to a user.

### The html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Custom Elements 101</title>
</head>
<body>
  <hello-component data-name="James"></hello-component>
  <script src="./hello-component.js"></script>
</body>
</html>
```

### The javascript
```javascript
class HelloComponent extends HTMLElement {
  static get observedAttributes() {
    return ['data-name'];
  }

  // custom methods
  render() {
    this.innerHTML = `Hello ${this.name}`;
  }

  get name() {
    return this.getAttribute('data-name');
  }

  // lifecycle hooks
  connectedCallback() {
    this.render();
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    this.render();
  }
}

// add into the 'real' dom as a valid tag
window.customElements.define('hello-component', HelloComponent);

```

Load the index.html and you should see "Hello James" displayed on the page.

Now, open the inspector (DevTools) and change the `data-name` attribute to something other than James. You will see we have inbuilt reactivity! Pretty sweet, right?

Granted, this is only a very basic, non-best-practice, 0 use case, default tutorial implementation but it does give you a rough introduction which we can build upon in future articles. 

## Browser support

Here is the current support for Web Components and all the APIs that facilitate them including Shadow DOM, Custom Elements (what we just looked into), HTML templates and slots and HTML imports:

![Current support for Web Components APIs from the caniuse documentation at the time of writing this article](https://preview.ibb.co/iFidQK/Capture.png)

## Conclusions

Custom Elements allow us a framework free way of implementing reactive UI's when *required*. They do provide us alot of challenges, many of which we will look at in the future, but go forth and try them out, look into the other Web Component APIs also as when put together, they really allow us to make cool, strong, reactive elements which can do alot with very little.

## Resources

- [Web Components - MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
- [Custom Elements - MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)
- [Shadow DOM - MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM)
- [HTML Templates and Slots - MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots)