Most code snippets used here are from the above series
Introduction
Web components are custom HTML Elements which are built with Javascript and make use of the Shadow DOM to encapsulate CSS and JS and user-defined HTML templates
At present Web Components are available in most major browsers with polyfills for IE and Edge
HTML Templates
HTML templates allow us to define reusable pieces of HTML that will not be rendered until used by a script
An template can be defined and used as follows
We can then create instances of the element by using javascript to use the template HTML and insert it into the UL
The importNode function takes in a fragment content and a boolean that tells the browser whether or not to copy just the parent or all of it’s subtree
Since templates are regulat HTML Elements, they can contain things like Javascript and CSS, for example:
The problem witht the above method is that the styles and functionality of the component can still impact the rest of the DOM once an instance/s are created
Custom Elements
Custom elements are elements that can be defined by users. These elements must have a - in their names
This markup can be shared between different frameworks
A custom element can be defined with
And can be used in HTML as follows
All Custom Elements must extend HTMLElement in order to be registered by the browser
The customElements API allows us to create custom HTML tags that can be used on any document that has the class definition for the element
Custom elements make use of lifecycle methods
The constructor is used to set up the basics of the element, and connectedCallback is used to add content to the element, set up event listeners or generally initialize the component
Typically an element’s state isn based on the attributes that are present on the element, for example a custom attribute open. We can watch changes to attributes in the attributeChangedCallback which is called whenever an element’s observedAttributes are changed
We can create a dialog component which makes use of the above
The attributeChangedCallback helps us to keep our internal element state and the external attributes in sync by updating out internal state when the external attributes are changed
Additionally we can create a getter and setter for the open property and make use of those to update the state using the following code
Using the above we can update the state based on the attribute as well as vice versa
Most elements will involve some boilerplate code to keep the element state in sync, we can instead encapsulate this functionality in an abstract class that we can extend for our custom elements, this will loop and allocate the respective attributes to the element state
Back to the dialog - we can add the ability for the dialog to show or hide itself by modifying it’s classes and add and remove the relevant event listeners
We also have the disconnectedCallback lifecycle method that allows us to do the necessary cleanup for the component
While the above helps us to encapsulate functionality, it say’s nothing of the stylings in the component which can still impact, and be impacted by the rest of the DOM
In order to do that we can make use of the Shadow DOM
Shadow DOM
The Shadow DOM is an encapsulated section of the DOM which helps to isolate pieces of the DOM including any CSS
When targeting the shadow DOM we make use of shadowRoot.querySelector where shadowRoot is a reference to the shadow-element
A fragment of ShadowDOM can be created by making use of attachShadow and the <slot></slot> element to include the content from the outer document
The above will render two buttons, the one in the shadow DOM will be blue, while the other will be unaffected by the CSS
By calling attachShadow with {mode: 'open'} we tell the element to save a reference to the shadow root which can be accessed with element.shadowRoot
If we use {mode: 'closed'} we will additionally need to store a reference to the root itself. We can do this using a WeakMap which uses the shadow root as the value and the element as the key
Usually we would not use a shadow root that is closed, this is more for elements like <audio> that use the shadow DOM for it’s implementation
The problem with using the shadow DOM is that the element needs to now interact with this instead of the light DOM. So the implementation needs to be updated as follows
We can also render content using <slot>, where we can include named slot’s in our light DOM, for example:
Which can then be rendered in it’s respective pieces in our element with
Furthermore, we can give the element a template (or different templates) by way of a new attribute for the element:
And then defining a render method to use that template with
Lastly, you can use attributeChangedCallback to update the component when the template is changed
Currently the only reliable way to style components is with the <style> tag, these can however make use of css variables which pass through into the shadow DOM
Proposed functionality
Constructible Stylesheets
This would allow stylesheets to be defined in JS and be applied on multiple nodes
This could potentially be used for the proposed CSS modules for example:
Part and Theme
The ::part() and ::theme() selectors could allow you to expose elements of a component for styling
::theme() is similar to ::part() but it allows elements to be styled from anywhere whereas the latter requires it to be specifically selected