Web Components are a set of web platform APIs that allow developers to create new custom, reusable, encapsulated HTML tags for web pages and applications. This technology is based on four main specifications:
- Custom Elements: This API enables the creation of custom HTML tags and the definition of new types of DOM elements. It allows for the extension of HTML with new elements, providing more semantic and powerful web pages.
- Shadow DOM: Shadow DOM enables web developers to encapsulate their custom element's styles and markup, avoiding style conflicts and ensuring components are self-contained. By attaching a shadow root in either "open" or "closed" mode, it controls access to the component's internals. "Open" mode allows manipulation via JavaScript, while "closed" mode offers stricter encapsulation by blocking external access to the shadow DOM.
- HTML Templates: The <template> and <slot> elements allow for the declaration of inert markup that can be activated and reused multiple times, reducing the need for script-based template systems.
Advantages of Web Components
- Encapsulation: By utilizing the Shadow DOM, Web Components ensure that their internal structure, styles, and behaviors are hidden from the rest of the application, reducing the risk of style conflicts and enhancing security.
- Reusability: Custom elements can be reused across different projects, making it easier to maintain a consistent look and feel across a developer's portfolio or within large applications.
- Interoperability: Web Components are designed to work across modern web frameworks and libraries, making them a versatile choice for developers working in diverse ecosystems.
- Future-proof: As a browser standard, Web Components are native to the web platform, reducing the need for external libraries and ensuring long-term compatibility.
To create a Web Component, start by defining a new class that extends HTMLElement. This class will encapsulate your component's functionality. Next, use the customElements.define() method to register your new element with the browser, passing in the element's name and its class. From there, you can add your component to any HTML page with its custom tag, and one of the key lifecycle callbacks is connectedCallback( ). This callback is invoked each time the custom element is added to the document's DOM. It's an ideal place to set up your component, initialise any required resources, or render its initial content.
Web Components offer a set of lifecycle callbacks (often referred to as "lifecycle hooks") that allow you to manage and respond to different stages in a component's lifecycle, from its creation to its removal from the DOM.
- constructor(): Called when an instance of the element is created or upgraded. Useful for initialising state, setting up event listeners, or creating a shadow DOM. This is where you should include setup work that doesn't involve the DOM, as the element isn't yet attached to the DOM.
- connectedCallback(): Invoked each time the custom element is appended into a document-connected element. This happens each time the node is moved, and may happen before the element's contents have been fully parsed. It's a good place to run setup code, like fetching resources or rendering. Essentially, it's useful for DOM-related initialisation.
- disconnectedCallback(): Called every time the element is removed from the DOM. It's useful for running clean up code (like removing event listeners or canceling any ongoing network requests).
- adoptedCallback(): Invoked each time the custom element is moved to a new document. This might occur if, for example, you used a custom element inside an iframe or when you use document.adoptNode() to move it into a new document.
- attributeChangedCallback(name, oldValue, newValue): Called when one of the custom element's attributes is added, removed, or changed. Which attributes to observe is specified in a static getter called observedAttributes. This callback is a good place to update the element's state or attributes in response to changes.
Creating a Todo App using Web Component
Integrating state management tools like Redux can enhance communication and data flow between components. So we will use Redux/Redux-Toolkit and Tailwind CSS for styling. This approach leverages the centralized store concept of Redux to manage the application's state, making it easier to maintain consistency across multiple components without direct parent-child communication or complex event handling.
In a Redux setup, the store and reducers play crucial roles:
- Store: The store is a centralized container that holds the entire state of your application. It allows access to the state, dispatches actions to update the state, and registers listeners. Essentially, the store is the heart of a Redux application, enabling state management in a predictable manner.
- Reducers: Reducers are pure functions that take the current state and an action as arguments and return a new state. They specify how the state changes in response to an action. Reducers are crucial for updating the state immutably, meaning they return new state objects instead of modifying the existing state directly.
TodoInput Component: This component enables users to enter new to-dos. It dispatches an addTodo action to the Redux store with the to-do item's information when a user submits the form. Additionally, it implements keyboard shortcuts for improved usability, such as focusing on the input field with specific key combinations and clearing it with the Escape key.
TodoList Component: This component displays the list of to-dos and allows users to delete them by dispatching deleteTodo actions to the Redux store. It listens to the Redux store's state changes, ensuring the to-do list is always up-to-date. The component also dynamically renders the to-dos, providing an interactive and responsive user experience.
The HTML snippet outlines a simple webpage setup that incorporates two custom Web Components for a to-do list application: todo-input-component for adding new tasks and todo-list-component for displaying the tasks.