lamplightdev

When are the constructor and connectedCallback methods called when creating a Custom Element?

Mon Mar 15 2021

The constructor and connectedCallback lifecycle methods of Custom Elements are called when your element is created and attached to the DOM respectively, but there are a few subtleties to keep in mind depending on how and when you define, create, insert and declare your element.

So how are Custom Elements defined, created, inserted and declared? And at which stage do the lifecycle methods get called?

Define

A custom element is defined when customElements.define is called:

class MyElement extends HTMLElement {
constructor() {
super();

console.log('constructed');
}

connectedCallback() {
console.log('connected');
}
}

customElements.define('my-element', MyElement);

Defining an element doesn't trigger either the constructor or the connectedCallback methods since it does not create an instance of an element.

An element can only be defined once.

Create

An element can be created in JavaScript in two ways:

// can happen before definition
const myElement = document.createElement('my-element');
// can only happen if already defined
const myElement = new MyElement();

Creation triggers the constructor, if the element has already been defined.

The constructor is only ever called once per element instance.

Insert

An element is inserted into the DOM imperatively with JS:

document.body.append(myElement);

Insertion triggers the connectedCallback method, if the element has already been defined.

connectedCallback is called each time the element is inserted into the DOM, with the disconnectedCallback method called each time it is removed from the DOM.

Declare

An element is declared when parsed as HTML:

<my-element></my-element>
document.body.innerHTML = '<my-element></my-element>';

Declaration triggers the constructor and connectedCallback methods, if the element has already been defined.

Upgrade

In all the above cases the lifecycle methods are only called if the element has already been defined. An element is upgraded when it already exists before definition - at the point of definition the constructor is then called automatically. If the element was already attached to the DOM at this point connectedCallback will also be called.

Examples

The examples below cover the different orders of the above stages to illustrate when the lifecycle methods are called.

Define then Declare
<html>
<head>
<script>
class MyElement extends HTMLElement {
constructor() {
super();

console.log('constructed');
}

connectedCallback() {
console.log('connected');
}
}

customElements.define('my-element', MyElement);
</script>
</head>
<body>
<my-element></my-element>
<!-- `constructor` then `connectedCallback` are called here when the element has been parsed -->
</body>
</html>
Declare then Define
<html>
<head> </head>
<body>
<my-element></my-element>

<script>
class MyElement extends HTMLElement {
constructor() {
super();

console.log('constructed');
}

connectedCallback() {
console.log('connected');
}
}

// UPGRADE
customElements.define('my-element', MyElement);
/**
`constructor` then `connectedCallback` are called here when the element has been defined -->
**/

</script>
</body>
</html>
Define then Create then Insert
<html>
<head> </head>
<body>
<script>
class MyElement extends HTMLElement {
constructor() {
super();

console.log('constructed');
}

connectedCallback() {
console.log('connected');
}
}

customElements.define('my-element', MyElement);

const myElement = document.createElement('my-element');
/**
`constructor` called here when element is created
**/


document.body.appendChild(myElement);
/**
`connectedCallback` called here when element is inserted
**/

</script>
</body>
</html>
Create then Insert then Define
<html>
<head> </head>
<body>
<script>
const myElement = document.createElement('my-element');

document.body.appendChild(myElement);

class MyElement extends HTMLElement {
constructor() {
super();

console.log('constructed');
}

connectedCallback() {
console.log('connected');
}
}

// UPGRADE
customElements.define('my-element', MyElement);
/**
`constructor` then `connectedCallback` are called here when the element has been defined -->
**/

</script>
</body>
</html>
Create then Define then Insert
<html>
<head> </head>
<body>
<script>
const myElement = document.createElement('my-element');

class MyElement extends HTMLElement {
constructor() {
super();

console.log('constructed');
}

connectedCallback() {
console.log('connected');
}
}

// UPGRADE
customElements.define('my-element', MyElement);
/**
`constructor` called here when the element has been defined
**/


document.body.appendChild(myElement);
/**
`connectedCallback` called here when the element has been defined -->
**/

</script>
</body>
</html>

Why does any of this matter?

Knowing when, how and why the constructor and connectedCallback methods are called is important when initialising your Custom Elements. Generally:


Get the latest Web Component news, tips and advice

I send out a regular newsletter - Web Component Bytes - containing curated news, tips and advice about Web Components.

Sign up below to get it hand delivered to your inbox - no spam, and unsubscribe at anytime.