In this post, we will talk about Composition, Shadow DOM, and Query Selectors in Lightning Web Components.
Composition in Lightning Web Components
Component Composition in LWC refers to adding of a component inside the body of another component. Composition permits you to build complex components from simple building-block components.
Advantages of Composition
- Composing, applications, and components from a collection of smaller components make code reusable and maintainable.
- Reduces the code size and improves code readability
Let’s take a look at a sample Books Display Application that is composed of components. This example is used to demonstrate the concept of owner and container.
Owner
The component which owns the template. In the below example, the owner is sampleBooksDisplayApp component. This component controls all the composed components (child) that are contained in it.
Owner has the capability to
- To set public properties on composed components
- To call methods on composed components
- To listen for any events triggered by composed components
Below is the code snippet that shows how Composition works in Lightning Web Components.
sampleBooksDisplayApp is parent component (owner) and it has sampleBookListCmp (child) component contained in it.
sampleBooksDisplayApp.html
<template>
<lightning-card title="Books Display" icon-name="utility:notebook">
<div class="slds-p-around_medium">
<c-sample-book-list-cmp books={booksList}></c-sample-book-list-cmp>
</div>
</lightning-card>
</template>
sampleBooksDisplayApp.js
import { LightningElement } from 'lwc';
export default class SampleBooksDisplayApp extends LightningElement {
booksList = ['The Alchemist' , 'The Lord of the Rings' , 'To Paradise'];
}
sampleBooksDisplayApp.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
sampleBookListCmp.html
<template>
<div class="slds-text-heading_medium">{books}</div>
</template>
sampleBookListCmp.js
import { LightningElement, api } from 'lwc';
export default class SampleBookListCmp extends LightningElement {
@api books = [];
}
Container
A Container contains other components but the owner component holds it.
Container has the capability to:
- Read the public properties but not change them
- To call methods on composed components
- Ability to listen to few events, bubbled up by the components that it contains
Shadow DOM in Lightning web components
Shadow DOM is a mechanism that encapsulates the internal DOM (Document Object Model, known as shadow tree) of a Web Component. In Lightning Web Components, elements are encapsulated in a shadow tree.
With Shadow DOM, elements of a component have consistent behavior and styling in any context. Encapsulation provides the ability to share and protect a component from manipulation from HTML, CSS & Javascript.
Shadow tree affects the way you work with CSS, Events & DOM. The below example contains two Lightning Web Components: c-todolist-app and c-todo-task. Shadow root will define the boundary b/w DOM and Shadow tree, which is known as the Shadow boundary.
<c-todolist-app>
#shadow-root
<div>
<p>Your To Do Tasks List</p>
</div>
<c-todo-task>
#shadow-root
<div>
<p>Go for a Walk</p>
</div>
</c-todo-task>
</c-todolist-app>
Below code snippet shows how shadow DOM works
<!DOCTYPE html>
<html>
<head>
<style>
div{
font-size: 50px;
color: darkgoldenrod;
}
</style>
</head>
<body>
<div id="DOMExample"></div>
<script>
const element = document.querySelector('#DOMExample')
const shadowRoot = element.attachShadow({mode:'open'})
shadowRoot.innerHTML = 'Shadow DOM Example'
</script>
</body>
</html>
Query Selectors in the Lightning Web Component
In order to access elements that are rendered by a component, we need to use the template property. Elements in a shadow tree cannot be accessed by traditional DOM querying methods. Use the below methods to check for the elements that a component rendered:
- this.template.querySelector();
- this.template.querySelectorAll();
Limitation
- -Order of the elements is not guaranteed
- -querySelector returns the results for the elements that are rendered to the DOM
- -it’s not a good approach to use ID selectors with querySelector. Generally, when a template is rendered, the IDs that are defined in HTML Templates are transformed into global unique values. Hence if you use an ID selector in Javascript, it doesn’t match with the transformed ID.
Below code snippet is an example for querySelector, on click of button text font and color is modified:
querySelectorSampleCmp.html
<template>
<lightning-card title="Query Selector DEMO in LWC">
<div class="slds-p-around_medium">
<p>How are you doing today?</p>
</div>
<div class="slds-p-left_medium">
<lightning-button label="Click Me" onclick={changeTextColor}></lightning-button>
</div>
</lightning-card>
</template>
querySelectorSampleCmp.js
import { LightningElement } from 'lwc';
export default class QuerySelectorSampleCmp extends LightningElement {
changeTextColor() {
const element = this.template.querySelector('p');
element.style.fontSize = '30px';
element.style.color = 'darkgoldenrod';
}
}
querySelectorSampleCmp.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
Output
Summary
I hope this helped you to understand the Composition, Shadow DOM and Query Selectors in Lightning Web Components.
Thank you for sharing this. I like how you can explain complex concepts using simple examples.