Custom Elements and Lifecycle Callbacks in JavaScript
Custom Elements are a foundational Web Components technology that
allows developers to define, encapsulate, and register their own
reusable HTML tags using modern JavaScript. This article explains the
fundamentals of Custom Elements and details how to implement their
standard lifecycle callbacks—connectedCallback,
disconnectedCallback, adoptedCallback, and
attributeChangedCallback—to manage component behavior
effectively across different states of the Document Object Model
(DOM).
What Are Custom Elements?
Custom Elements are custom HTML tags created by extending the base
JavaScript HTMLElement class and registering them with the
browser via the customElements.define() method. They enable
developers to build modular, framework-agnostic user interface
components with customized behavior and styling.
Custom Elements must contain a hyphen in their name (e.g.,
<user-card>) to avoid naming collisions with standard
HTML tags.
Custom Element Lifecycle Callbacks
The browser executes specific lifecycle methods automatically at distinct stages of an element’s existence in the DOM. Implementing these callbacks allows developers to control rendering, initialize resources, and clean up memory.
1. constructor()
The constructor runs when an instance of the custom element is
created (either via document.createElement() or when parsed
from HTML).
- Purpose: Initialize internal state, attach a Shadow DOM, and set up event listeners that do not depend on DOM insertion.
- Requirement: Must invoke
super()first to inherit properHTMLElementfunctionality. DOM attributes and children should not be accessed or manipulated here.
2. connectedCallback()
Triggered every time the element is inserted into the DOM.
- Purpose: Render dynamic UI, fetch data, query child
elements, and set up external event listeners (such as
windowresize listeners) or timers.
3.
disconnectedCallback()
Triggered every time the element is removed from the DOM.
- Purpose: Clean up resources to prevent memory leaks, such as clearing active intervals, aborting fetch requests, and removing global event listeners.
4. adoptedCallback()
Triggered when the element is moved to a new document (such as when
moved between different <iframe> elements using
document.adoptNode()).
- Purpose: Re-initialize document-specific context or resources if the host document context changes.
5.
attributeChangedCallback(name, oldValue, newValue)
Triggered when an observed attribute is added, removed, updated, or replaced on the element.
- Purpose: React to external configuration changes and update internal component state or UI accordingly.
- Requirement: The component must define a
static get observedAttributes()method that returns an array of attribute names to monitor.
Implementation Example
Below is a complete implementation demonstrating how to create a custom element and define each lifecycle callback:
class CounterElement extends HTMLElement {
// Define attributes to monitor
static get observedAttributes() {
return ['count'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
disconnectedCallback() {
// Perform cleanup tasks here
}
adoptedCallback() {
// Handle adoption into a new document
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
render() {
const count = this.getAttribute('count') || 0;
this.shadowRoot.innerHTML = `
<style>
div { font-family: sans-serif; padding: 8px; }
</style>
<div>Current Count: <strong>${count}</strong></div>
`;
}
}
// Register the custom element with the browser
customElements.define('counter-element', CounterElement);Using this element in HTML is as straightforward as writing:
<counter-element count="5"></counter-element>Whenever the count attribute is modified dynamically via
JavaScript (e.g., element.setAttribute('count', '10')),
attributeChangedCallback executes and re-renders the
element’s shadow content.