visit
Custom elements in HTML are a way to extend native HTML elements. Javascript frameworks simulate the behavior of components in a web page whereas custom elements provide a native HTML-ly way to do so. A web component uses custom elements along with other techniques, such as the shadow DOM.
class Demo extends HTMLElement {
constructor() {
super()
}
connectedCallback() {
this.textContent = "hello"
}
}
customElements.define("demo", Demo)
<demo></demo>
Uncaught SyntaxError: Failed to execute 'define' on 'CustomElementRegistry': "demo" is not a valid custom element name
So now, if you change it to something like demo-webc
and change the class name to DemoWebC
, it works.
class DemoWebC extends HTMLElement {
constructor() {
super()
this.customProperty = "custom"
}
connectedCallback() {
this.textContent = "hello"
}
}
// two arguments: tag name, class name
customElements.define("demo-webc", DemoWebC)
It is always recommended to call the super()
method first in the constructor as it initializes the default properties of the HTMLElement
class by invoking its constructor.
The connectedCallback()
method is for detecting when the element is loaded into the page. There's also a method named disconnectedCallback()
which detects if the element is removed from the page. A third method name adoptedCallback()
says that the element has moved to a new page.
constructor() {
super()
this.customProperty = {
name: "data-custom",
value: "custom value"
}
connectedCallback() {
this.textContent = "hello"
this.setAttribute(this.customAttribute.name, this.customAttribute.value)
}
}
But what if you need to modify the functionality of the attribute's value change? That's where attributeChangedCallback()
method comes into action. In order to see it in action, you need to first define a static observedAttributes
class property and set it to an array of all the attributes you want to keep track of.
The attributeChangedCallback()
fires if those attributes mentioned in the static observedAttributes
property change. Note that if the attribute is already present when the custom element loads, this method is fired at that time, too.
static observedAttributes = ["data-custom"]
constructor() {
super()
}
attributeChangedCallback(name, old, newValue) {
console.log(name, old, newValue)
}
Once you are done with building a custom element, you must register it by using the define()
method. It's callable on the customElements
global object (window.customElements
), which is a registry of custom elements.
customElements.define("custom-element-name", ClassName, options)
For that, instead of extending the HTMLElement
class, extend the HTMLAnchorElement
class. And specify which type of HTML element it extends with the extends
option.
class DemoAnchor extends HTMLAnchorElement {
constructor() {
super()
}
connectedCallback() {
this.textContent = "syntackle.live"
this.href = "//syntackle.live"
}
}
customElements.define("demo-anchor", DemoAnchor, { extends: "a" })
You can't use this element like <demo-anchor>
because it's not an autonomous element, instead you can use it like this:
<a is="demo-anchor"></a>
Web components are more than just custom elements. They sometimes also involve a shadow DOM. A "shadow" DOM, as the name suggests, is a sub-DOM tree for HTML elements. It is mainly used for encapsulation and restricting styles up to the web component only.
You can access elements outside the shadow DOM from inside the shadow DOM.
class DemoWebC extends HTMLElement {
constructor() {
super()
}
connectedCallback() {
const shadow = this.attachShadow({mode: "open"})
const style = document.createElement("style")
style.textContent = `p { color: blue; }`
shadow.appendChild(style)
const text = document.createElement("p")
text.textContent = "hello"
shadow.appendChild(text)
}
}
customElements.define("demo-webc", DemoWebC)
Shadow DOM has two modes: open
and closed
. Open means external elements in the page can modify the contents of the shadow DOM by using shadowRoot
property. In the closed
mode, the shadow DOM is not accessible from outside using the shadowRoot
property as it is null
in this case.
console.log(document.querySelector("demo-webc").shadowRoot)
It returns null
.
Templates and slots are extremely useful when building complex custom elements or web components. Diving deep into them is out of the scope of this article, but here are some good resources for them:
Constructing a CSSStyleSheet
object, inserting CSS in it using replaceSync()
and attaching it to the shadow DOM using the adoptedStyleSheets
property.
const shadowDOM = this.attachShadow({mode: "open"})
const styleSheet = new CSSStyleSheet()
styleSheet.replaceSync(p { color: blue; })
shadowDOM.adoptedStyleSheets = [styleSheet]
Declaring styles using a <template>
.
<template id="custom">
<head>
<style>p { color: blue; }</style>
</head>
<p>Web Component</p>
</template>
const shadowDOM = this.attachShadow({ mode: "open" })
const template = document.querySelector("#custom")
shadowDOM.appendChild(template.content.cloneNode(true))
Simply creating a style
tag and inserting CSS as text in it.
const style = document.createElement("style")
style.textContent = p { color: blue; }
shadow.appendChild(style)
The first web component shown below is a custom button element, which opens a dialog
element. And the second web component involves a shadow DOM to pretty print JSON strings in HTML.
Also published .