Skip to main content Accessibility Feedback

Handling asychronous rendering in Web Components

I’m working on a Web Component for a client project where the component may be created and injected into the DOM before some required attributes are added to it.

But, the Web Component requires that attributes to render it’s content properly.

Normally I run that stuff in the constructor() (or if needed, the connectedCallback()). For this component, sometimes that will work, but sometimes it won’t.

Today, I wanted to share how to I deal with this.

๐Ÿ‘‹ Need help on a project? I have one consulting spot left, and would love to work with you!

An example

Let’s imagine we have a <say-hi> component that accepts a [person] attribute.

<say-hi person="Chris"></say-hi>

When it renders, it displays a greeting with the value of [person] attribute.

customElements.define('say-hi', class extends HTMLElement {

	// Instantiate the component
	constructor () {
		super();
		let person = this.getAttribute('person');
		this.textContent = `๐Ÿ‘‹ Hello ${person}!`;
	}

});
````

The finished HTML looks like this...

```html
<say-hi person="Chris">
	๐Ÿ‘‹ Hello Chris!
</say-hi>

The <say-hi> custom element may be hard-coded in the HTML, in which case rendering the text in the constructor() works just fine.

But what if I create it dynamically with JavaScript?

let merlin = document.createElement('say-hi');
merlin.setAttribute('person', 'Merlin');

Here, the constructor() runs when the document.createElement() method creates the new <say-hi> element.

But at that point, the [person] attribute hasn’t been added to the element yet. That happens later.

The resulting HTML would say ๐Ÿ‘‹ Hello null!.

Dealing with async rendering

The attributeChangedCallback() method runs whenever one of a predefined list of attributes on the element changes.

// Runs when the [person] attribute changes
attributeChangedCallback (name, oldValue, newValue) {
	// ...
}

// Create a list of attributes to observe
static get observedAttributes () {
	return ['person'];
}

But it’s also fired when a pre-rendered HTML element is loaded into the DOM.

The browser creates the element and adds the attribute, triggering the attributeChangedCallback().

If we shift our rendering code there, it works for all situations.

customElements.define('say-hi', class extends HTMLElement {

	// Instantiate the component
	constructor () {
		super();
	}

	// Runs when the value of an attribute is changed on the component
	attributeChangedCallback (name, oldValue, newValue) {
		if (!newValue.length) return;
		this.textContent = `๐Ÿ‘‹ Hello ${newValue}!`;
	}

	// Create a list of attributes to observe
	static get observedAttributes () {
		return ['person'];
	}

});

Here’s a demo.