Using innerHTML
and template literal strings with no sanitization may cause security issues with script injection. In the past, developers used various workarounds to implement HTML templates, but all of these workarounds had issues.
This is where the <template>
element comes in; templates provide truly inert DOM, a highly performant method to clone nodes, and reusable templating.
Next, transition the component to use HTML Templates:
<body>
<template id="rating-element-template">
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating"></span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
</template>
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
</body>
Here you moved the DOM content into a <template>
tag in the main document's DOM. Now refactor the custom element definition:
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
const templateContent = document.querySelector<HTMLTemplateElement>('#rating-element-template')!.content;
const clonedContent = templateContent.cloneNode(true);
shadowRoot.appendChild(clonedContent);
this.shadowRoot!.querySelector<HTMLElement>('.rating')!.innerText = `${this.rating}`;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
const templateContent = document.querySelector('#rating-element-template').content;
const clonedContent = templateContent.cloneNode(true);
shadowRoot.appendChild(clonedContent);
this.shadowRoot.querySelector('.rating').innerText = `${this.rating}`;
}
To use this <template>
element:
templateContent.cloneNode
true
argument performs a deep clone.Congratulations, now your web component's DOM is encapsulated, but the DOM is still static. In the next steps, you'll add support for updating the rating.