Custom Elements
Web Components: Custom Elements
What are the two types of custom elements in web development?
View Answer:
Interview Response: We classify custom elements into two groups: autonomous custom elements and modified built-in components. Autonomous custom elements — elements that are "all-new" and extend the abstract HTMLElement class. Customized built-in elements — extending built-in components, such as a customized button based on HTMLButtonElement.
What are the requirements needed to create a custom element?
View Answer:
Interview Response: To create a custom element, we need a class extension, like HTMLElement, and a customElement defined to register the new element. These requirements cover both the customized and autonomous elements. In addition, there are several methods that we can use that are optional, like connectedCallBack, for custom elements.
Code Example:
class MyElement extends HTMLElement {
constructor() {
super();
// element created
}
}
// let the browser know that <my-element> is served by our new class
customElements.define('my-element', MyElement);
What naming standard should be used for custom elements in web development?
View Answer:
Interview Response: Custom element names must have a hyphen (-) e.g., my-element and super-button are valid names, but myelement is not. That is to ensure no name conflicts between built-in and custom HTML elements.
What are the five methods in the lifecycle callbacks?
View Answer:
Interview Response: The five methods included in the lifecycle callbacks are the connectedCallback, disconnected, adoptedCallback, attributeChangedCallback, and the observedAttributes methods.
Can you describe the connectedCallBack method's purpose?
View Answer:
Interview Response: The connectedCallBack invokes each time the custom element appends into a document-connected element. This action happens each time the node moves and before the element's contents completely propagates.
What happens when a base element, the one we are customizing, loads before the customized element?
View Answer:
Interview Response: If the browser encounters any elements we are trying to customize before customElements.define, that is not an error. But the element is yet unknown, just like any non-standard tag.
Such “undefined” elements can be styled with CSS selector :not(:defined).
When customElement.define is called, they are “upgraded”: a new instance of the element we are trying to customize gets created for each, and connectedCallback gets called. They become :defined.
Such “undefined” elements can be styled with CSS selector :not(:defined).
When customElement.define is called, they are “upgraded”: a new instance of the element we are trying to customize gets created for each, and connectedCallback gets called. They become :defined.
What is the reasoning for not utilizing the constructor and instead relying on connectedCallBack?
View Answer:
Interview Response: The reason is simple: it is too early when the constructor gets called. The element gets created, but the browser did not yet process/assign attributes at this stage: calls to getAttribute would return null. So, we cannot render there. Besides, if you think about it, it is better to delay the work until needed.
When the element gets added to the document, the connectedCallback is triggered. It is not just attached to another element as a child but instead becomes a part of the page. As a result, we may construct detached DOM, create elements, and prepare them for subsequent usage. They do not render until they get included on the page.
When the element gets added to the document, the connectedCallback is triggered. It is not just attached to another element as a child but instead becomes a part of the page. As a result, we may construct detached DOM, create elements, and prepare them for subsequent usage. They do not render until they get included on the page.
Can you explain how observedAttribute works in conjunction with attributeChangedCallback?
View Answer:
Interview Response: When one of the custom element's attributes gets added, deleted, or updated, the attributeChangedCallback gets called. We may observe attributes by passing a list of them to the observedAttributes() static getter. When such attributes are adjusted, attributeChangedCallback invokes.
Code Example:
<script>
class TimeFormatted extends HTMLElement {
render() {
// (1)
let date = new Date(this.getAttribute('datetime') || Date.now());
this.innerHTML = new Intl.DateTimeFormat('default', {
year: this.getAttribute('year') || undefined,
month: this.getAttribute('month') || undefined,
day: this.getAttribute('day') || undefined,
hour: this.getAttribute('hour') || undefined,
minute: this.getAttribute('minute') || undefined,
second: this.getAttribute('second') || undefined,
timeZoneName: this.getAttribute('time-zone-name') || undefined,
}).format(date);
}
connectedCallback() {
// (2)
if (!this.rendered) {
this.render();
this.rendered = true;
}
}
static get observedAttributes() {
// (3)
return [
'datetime',
'year',
'month',
'day',
'hour',
'minute',
'second',
'time-zone-name',
];
}
attributeChangedCallback(name, oldValue, newValue) {
// (4)
this.render();
}
}
customElements.define('time-formatted', TimeFormatted);
</script>
<time-formatted id="elem" hour="numeric" minute="numeric" second="numeric">
</time-formatted>
<script>
setInterval(() => elem.setAttribute('datetime', new Date()), 1000); // (5)
</script>
note
It does not trigger unlisted properties (for performance reasons).
Can you explain the rendering order when the HTML parser builds the DOM?
View Answer:
Interview Response: When the HTML parser builds the DOM, elements are processed and parents before children. E.g., if we have ‹outer›‹inner›‹/inner›‹/outer›, then ‹outer› element is created and connected to DOM first, and then ‹inner›. That leads to important consequences for custom elements that we should prepare for in our code.
Code Example:
<script>
customElements.define(
'user-info',
class extends HTMLElement {
connectedCallback() {
alert(this.innerHTML); // alert is empty (*)
}
}
);
</script>
<user-info>John</user-info>
Is there a way to ensure that custom-element returns a value on a nested element?
View Answer:
Interview Response: When the HTML parser builds the DOM, elements are processed and parents before children. E.g., if we have ‹outer›‹inner›‹/inner›‹/outer›, then ‹outer› element is created and connected to DOM first, and then ‹inner›. That leads to important consequences for custom elements that we should prepare for in our code. To handle inner elements, we can delay actions using setTimeout to ensure that the DOM has completed loading our document. If we want to pass information to custom-element, we can use attributes. They are available immediately, or if we need the children, we can defer access to them with zero-delay setTimeout.
Code Example:
<script>
customElements.define(
'user-info',
class extends HTMLElement {
connectedCallback() {
setTimeout(() => alert(this.innerHTML)); // John (*)
}
}
);
</script>
<user-info>John</user-info>
Are there any issues with new or autonomous elements relative to search engines?
View Answer:
Interview Response: Yes, a new or autonomous element like ‹my-element› does not give a search engine enough information, like associated semantics. The elements are unknown to search engines, and accessibility devices cannot translate them. We can extend and customize built-in HTML elements by inheriting them from their classes to fix this.
Code Example:
<script>
// The button that says "hello" on click
class HelloButton extends HTMLButtonElement {
constructor() {
super();
this.addEventListener('click', () => alert('Hello!'));
}
}
customElements.define('hello-button', HelloButton, { extends: 'button' });
</script>
<button is="hello-button">Click me</button>
<button is="hello-button" disabled>Disabled</button>
<user-info>John</user-info>