Web Components API: Definition, Attributes And Props
Tags: javascript, html, customelementsv1, stencil
Published: 2021-07-20 09:59:56

After learning what [Web Components](https://javascript.works-hub.com/learn/web-components-the-vanilla-framework-8b60b) are, their history and benefits, it's time to dive deeper into their API component and start doing some coding.

In the following sections, we will go through the key concepts of the Web Component API, which allow us to create a Web Component:

- Definition, attributes and props
- Template, Slots, ShadowDOM vs LightDOM
- Lifecycle events and custom events
- How to style Web Components

Today we'll try to define web components and talk about two of their most important and sometimes hard to understand characteristics - **attributes and properties**.

Before we get started, we need to take a quick detour to a concept mentioned in the [last article](https://javascript.works-hub.com/learn/web-components-the-vanilla-framework-8b60b): **custom elements**.

First things first: how can a custom element be defined?

## Custom elements: definition and creation

The web components creation is based on the standard ES6 Class API. As seen before, custom elements allow the definition of custom HTML tags (and therefore, DOM elements) and their attributes. Despite being custom, they are standard as well, that's why they extend for `HTMLElement`:

```js
class MyCustomElement extends HTMLElement {
  // our custom element code
}
```

The line above is both simple and powerful. It allows us to create native HTML elements as if they were part of the official standard.

That is not all, not only are we able to create our custom elements but we can also extend the existing ones. To do that, instead of extending from the generic class `HTMLElement`, we have to do it from the class we want to extend from. For instance, if we want to create a custom element that extends the functionality of the native anchor element, we could do it by defining our custom element as follows:

```js
class MyCustomElement extends HTMLAnchorElement {
  // our custom element code
}
```

This is what we call **customized built-in elements**. We'll see more of it later when we talk about web components' composition.

As our custom element class is extending from a different one, in order to follow the inheritance hierarchy, it is required to establish the correct prototype chain and set `this` value before any further code is run, to make a parameter-less call to `super()` as the first statement in the constructor body.

```js
class MyCustomElement extends HTMLElement {
  constructor() {
    super();
  }
  // our custom element code
}
```

Once we have our custom element class ready with all its features and behaviour already coded, we need to define the custom element. So the full example would look like this:

```js
class MyCustomElement extends HTMLElement {
  constructor() {
    super();
  }
  // our custom element code
}

customElements.define('my-custom-element', MyCustomElement);
```

If you're a _one-liner_ lover, the one-line version is possible by using an anonymous class:

```js
customElements.define('my-custom-element', class extends HTMLElement {
    constructor() {
      super();
    }
    // our custom element code
  }
);
```

Apart from the `define` method, there are other methods available in the `customElement` object:

- `get(name)`: If a custom element with the given _name_ it has been defined, it will return its constructor, otherwise`undefined` 
- `whenDefined(name)`: Returns a promise that will resolve when the custom element with the given _name_ is defined. It will reject if the given _name_ is not valid or no custom element exists. This method can be used to avoid executing an action until the target custom elements are defined.
- `upgrade()`: Allows upgrading elements. Elements are automatically upgraded when they become connected, but this method can be used if you need to upgrade before you connect the element. It's not very common to use it but it's available when needed.

Notice that we are using the `customElements` object directly without defining it. That's possible because this object is available on the global Browser Window Object `window`. This object is always available in the browser environment in the global scope. When the JavaScript engine doesn't find a variable in the current scope it finds them in the global one, and specifically in the `window` object. That's why, in our case, typing `customElements.define(...)` and `window.customElements.define(...)`is the same.

It's also worth mentioning two other important features that we might cover in a future article - forms and accessibility features.

To make a custom element a form-associated custom element, just add the following static method (returning true)

```js
static get formAssociated() { return true; }
```

On the other hand, the `ElementInternal` interface has functions and properties useful both for form elements as well as for accessibility features. To get an instance of it, call the `attachInternals()` method:

```js
this._internals = this.attachInternals();
```

You can get more info about `ElementInternal` on the [WHATWG site](https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals) (Web Hypertext Application Technology Working Group).


Finally, it's important to highlight some key points that need to be taken into account when creating custom elements:

- The name of the customs element **must contain a hyphen/dash (-)**, therefore, it has to be formed of at least two words. This requirement was defined in order to make the HTML parser able to distinguish custom elements from native ones. As you probably have already realized, native elements tags are "one-word" tags.
- The name of the custom element **must be in lowercase**, i.e. it has to follow the _kebab-case_ convention.
- The name of the tag and its class, doesn't have to be the same, but it's recommended for them to coincide. For clarity, some authors, like to add the _Component_ sufix to the class name.
- Tags can not be registered more than once, so, obviously, different custom elements cannot share the same tag definition.
- Custom elements cannot be self-closing by definition. Only void elements can be self-closing. You can read more about self-closing elements and their complete list in the [official HTML SPEC](https://html.spec.whatwg.org/multipage/syntax.html#void-elements)

To be clear about naming custom elements, let's take a look at some invalid/valid examples:

- `<mycomponent>` --> `<my-component>`
- `<MyComponent>` --> `<my-component>`
- `<My-Component>` --> `<my-component>`
- `<MyCustomComponent>` --> `<my-customcomponent>` or `<my-custom-component>`

## Attributes

**Attributes** are a HTML related concept. Attributes are the way we define the characteristics of an HTML element, i.e., an HTML tag. The same applies to Web Components.

In the example below, `src`, `alt`, `width`and `height` are attributes. 

```html
<img src="/img/profile/MartyMcfly.png" alt="A profile picture of Marty McFly" width="200" height="200" />
```

When the browser engine parses the HTML code to create DOM objects, it recognizes standard attributes and creates DOM properties from them. It's worth to highlight that this only occurs with standard attributes, not with custom ones. Please note that standard attributes are not the same for all elements. For instance, `id`is a standard attribute common for all the elements, while `alt` attribute is exclusive of `<img />`.

For the custom attributes that are not automatically reflected properties, as well as for operating with attributes in general, we have the following set of methods available:

- `element.hasAttributes()`: Checks whether the element has attributes or not.
- `element.hasAttribute(name)`: Checks whether the element has the attribute with provided _name_.
- `element.setAttribute(name, value)`: Checks the attribute called _name_ with the given _value_.
- `element.getAttribute(name)`: Return the value of the attribute called _name_ or null if it doesn't exist.
- `element.getAttributeNames()`: Returns an array of the element's attributes.
- `element.toggleAttribute(name)`: Toggles boolean attributes the following way: removes them if they exist, creates them if not.

One thing to be careful about is defining your own attributes for the web component. They don't have to collide with any native attributes and shouldn't confuse the developer about their nature (native or custom). On the other hand, it's important to highlight that attributes should be used only with primitives types as `string`, `number` and `boolean`.

### Listening to attributes changes in web components

When developing a web component, we'll probably need to execute actions when some attribute change. For instance, if we have a value attribute and this is set, we'll want to render its (new) value. The custom elements API brings us a way to observe attributes changes. It consists of two related methods: `observedAttributes` and `attributeChangedCallback`.

The first one is where we define which attributes we want to observe. To do that we have to return an array with the names of the attributes. In the following example, we want to observe changes in attributes called `value`, `min` and `max`:

```js
static get observedAttributes() { 
  return ['value', 'min', 'max']; 
}
```

The other method allows us to execute actions when an attribute has changed. It takes into account three parameters: 

1. The name of the attribute that changed and therefore has fired the callback,
2. The old value of the attribute that changed,
3. The new value of the attribute that changed.

Find below an example of a attribute called "value" that has changed from 7 to 11:

```js
attributeChangedCallback(name, oldValue, newValue) {
  // name will be "value"
  // oldValue will be "7" 
  // newValue will be "11" 
  
  // your code...
}
```

## Properties

**Properties** are a JavaScript-related concept. They are part of the DOM Node interface, an abstract base class upon which many other DOM API objects are based (more info about it in ([MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Node#properties)). They are used to access to DOM Node interface features, like `Node.parentNode`, `Node.childNodes`, `Node.className` and many more. We can also define our custom properties, to define the custom behaviours of our custom elements.

Properties can have any value and they are case sensitive.

## Reflecting properties to attributes (and vice-versa)

In some way, we have already described the relation between properties and attributes. Properties are the Javascript representation of HTML tags attributes. Meaning that when an attribute is modified, we expect that its property value will be also modified and vice-versa. 

You can check this behaviour with the following snippet, where you will see that the `src` attribute will change after modifying the property value (after 1'5 seconds to be able to see the original value).

```html
  <img id="myImg" src="yourPicture.jpg"/>
```
```js
setTimeout(() => {
  document.querySelector('#myImg').src="otherPicture.jpg";
},1500)
```

As previously mentioned, the browser engine creates properties for those standard attributes that it recognizes, but not for the custom ones. For the custom ones, we need to do it explicitly when defining our custom element.

We could do that by defining getter and setter methods for the properties we would like to reflect its value to its namesake attributes. To do the magic, we need to set and get the attribute value under the hood.

Let's suppose that we have a custom element whose class has a property called `color` that we want to reflect its namesake property. Give this scenario, the code would be the following :

```js
//'this' is pointing to the custom-element declared class 
// that extends from HTMLElement, that's why has the 
// 'setAttribute' and 'getAttribute' methods available

set color(value){
  this.setAttribute('color', value)
}

get color(){
  this.getAttribute('color')
}
```

Behind the scenes, what is happening is that when we execute `this.color = 'blue'` what is really being executed is a call to color's `set` method with a param value of 'blue', which will set the attribute's value to 'blue'. On the other hand, when we execute `this.color` what is really being executed is a call to color's `get` method, which return the attribute's value.

If you don't remember well how property getters and setters work, you can have a look at [MDN Web Docs](https://developer.mozilla.org/en-us/docs/Web/JavaScript/Guide/Working_with_Objects#defining_getters_and_setters)

All these attributes and properties are a bit hard to understand by just looking at the code, it will get easier once you'll see it working in a real custom element, something we'll do in the next chapters. Meanwhile, remember the trick to achieve reflection: linking get/set property methods to get/set attribute methods

Maybe this schema will help clarify how attributes and properties reflection works:

![Untitled Diagram-2.png](https://functionalworks\-backend\-\-prod.s3.amazonaws.com/logos/6c1fbfe57d9771e6d90a6c82cf788dbb)

## Conclusion

We had a great tour of the minimum we need to build a web component. So far we know how to define a custom element and its attributes/properties. In the next chapter, we'll talk about templates, slots and ShadowDOM vs LightDOM.

Make sure to read the first article of this series as well:

- [Web Components: The vanilla framework](https://javascript.works-hub.com/learn/web-components-the-vanilla-framework-8b60b)

**Don't forget to [sign up](https://works-hub.com/register?utm_source=blog&utm_medium=cta&utm_campaign=blog-sign-ups) to WorkHub for more articles like these!**

Originally written by [Rafa Romero Dios](https://javascript.works-hub.com/users/3e58cda8-299f-41b4-8fd7-f2fd7a988d9d?utm_source=dev_to&utm_medium=blog_xpost) for [JavaScript Works](https://javascript.works-hub.com/learn/web-components-api-definition-attributes-and-props-886c0?utm_source=dev_to&utm_medium=blog_xpost)