Chapter 6: Web components
6.1 From the orbital height
This section describes a set of modern standards for “web components”.
Component architecture
A good architect is the one who can make the complex simple.
We can split user interface into visual components: each of them has own place on the page, can “do” a well-described task, and is separate from the others.
A component has:
DOM structure: managed solely by its class, outside code doesn’t access it (encapsulation).
CSS styles: applied to the component.
API: events to interact with other components.
Custom elements: Define custom HTML elements.
Shadow DOM: Create an internal DOM for the component, hidden from the others.
CSS Scoping: Declare styles that only apply inside the Shadow DOM of the component.
Event retargeting: Make custom components better fit the development.
6.2 Custom elements
We can create custom HTML elements, described by our class, with its own methods and properties, events and so on.
Autonomous custom elements
: "all-new" elements, extending the abstractHTMLElement
class.Customized built-in elements
: extending built-in elements, like a customized button, based onHTMLButtonElement
etc.
Autonomous elements
To register the element:
Custom element name must contain a hyphen -
.
Example: “time-formatted”
Custom elements upgrade
If the browser encounters any customzied elements before customElements.define
, that’s not an error.
When customElement.define
is called, they are upgraded.
Rendering in connectedCallback, not in constructor
Element content is rendered in connectedCallback
.
When constructor
is called, the element is created, but the browser did not yet process/assign attributes.
The connectedCallback
triggers when the element is added to the document. Not just appended to another element as a child, but actually becomes a part of the page. Thus we can build detached DOM, create elements and prepare them for later use.
Observing attributes
We can observe attributes by providing their list in observedAttributes()
static getter. attributeChangedCallback
is called when they are modified.
Rendering order
When HTML parser builds the DOM, elements are processed one after another, parents before children.
We can defer access to the children with zero-delay setTimeout.
Customized built-in elements
Customized elements don’t have any associated semantics. They are unknown to search engines, and accessibility devices can’t handle them.
However, we can extend and customize built-in HTML elements by inheriting from their classes.
6.3 Shadow DOM
Shadow DOM serves for encapsulation.
Built-in shadow DOM
The browser uses DOM/CSS internally to draw it.
We can’t get built-in shadow DOM elements by regular JavaScript calls or selectors.
Shadow tree
A DOM element can have two types of DOM subtrees:
Light tree: a regular DOM subtree, made of HTML children.
Shadow tree: a hidden DOM subtree, not reflected in HTML.
Shadow tree can be used in Custom Elements to hide component internals and apply component-local styles.
We can create only one shadow root per element.
The mode
option sets the encapsulation level.
open
: the shadow root is available aselem.shadowRoot
.close
:elem.shadowRoot
is always null. We can only access the shadow DOM by the reference returned byattachShadow
.
Encapsulation
Not visible to
querySelector
from the light DOM.Style rules from the outer DOM don’t get applied.
6.4 Template element
A built-in <template>
element serves as a storage for HTML markup templates.
<template>
content can be any syntactically correct HTML.<template>
content is considered out of the document.We can access
template.content
to clone it to reuse in a new component.
Inserting template
The template content is available in its content property as a DocumentFragment
.
When we insert it somewhere, its children are inserted instead.
6.5 Shadow DOM slots, composition
Shadow DOM supports <slot>
elements, that are automatically filled by the content from light DOM.
Named slots
The flattened DOM exists only for rendering and event-handling purposes. JavaScript won't see it.
The slot="..."
attribute is only valid for direct children of the shadow host.
If we put something inside a <slot>
, it becomes the fallback, “default” content.
Default slot: first unnamed
The first <slot>
in shadow DOM that doesn’t have a name is a “default” slot. It gets all nodes from the light DOM that aren’t slotted elsewhere.
Updating slots
The browser monitors slots and updates the rendering if slotted elements are added or removed. The slotchange
event is triggered.
Slot API
node.assignedSlot
: returns the<slot>
element that the node is assigned to.slot.assignedNodes({flatten: true/false})
: DOM nodes assigned to the slot.slot.assignedElements({flatten: true/false})
: only element nodes.
6.6 Shadow DOM styling
:host
The :host
selector allows to select the shadow host.
Cascading
If there’s a property styled both in :host
locally, and in the document, then the document style takes precedence.
:host(selector)
Same as :host
, but applied only if the shadow host matches the selector
.
:host-context(selector)
Same as :host
, but applied only if the shadow host or any of its ancestors in the outer document matches the selector
.
Styling slotted content
Slotted elements come from light DOM, so they use document styles. Local styles do not affect slotted content.
CSS hooks with custom properties
Custom CSS properties exist on all levels, both in light and shadow.
6.7 Shadow DOM and events
Events that happen in shadow DOM have the host element as the target, when caught outside of the component.
Event retargeting is a great thing to have, because the outer document doesn’t have to know about component internals. Retargeting does not occur if the event occurs on a slotted element, that physically lives in the light DOM.
Bubbling, event.composedPath()
For purposes of event bubbling, flattened DOM is used.
event.composed
Most events successfully bubble through a shadow DOM boundary. If event.composed
is true
, then the event does cross the boundary.
Last updated