Understanding Slot Updates with Web Components
Tags: webcomponents, javascript
Published: 2021-09-30 02:26:50

---
title: Understanding Slot Updates with Web Components
published: true
date: 2020-08-31 05:00:00 UTC
tags: webcomponents, javascript
canonical_url: https://coryrylan.com/blog/understanding-slot-updates-with-web-components
---

Web Components provide a component model to the Web. Web Components, instead of being a single spec, is a collection of several stand-alone Web technologies. Often Web Components will leverage the Shadow DOM feature. Shadow DOM is commonly used for CSS encapsulation. However, Shadow DOM has another useful feature called Slots.

The Slots API is a content projection API that allows HTML content from the host application to be rendered into your component template. Common examples of this are things like cards and modals.

Here is a minimal example of a Custom Element using the Slot API.

```javascript
const template = document.createElement('template');
template.innerHTML = `
<div class="inner-template">
  <slot></slot>
</div>`;

class XComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}

customElements.define('x-component', XComponent);


<x-component>
  <p>Some Content</p>
</x-component>
```

The tags content can be rendered into our template we defined and marked with the `<slot>` element. If we look at what the browser renders, we will see something like this:

![Custom Element Slot](https://coryrylan.com/assets/images/posts/2020-08-31-understanding-slot-updates-with-web-components/custom-element-slot.png)

The content is projected and rendered within the template of our component. Often there are use cases, whereas the component author we would like to know about any updates to the content provided by the slot element. We can achieve this by adding an event listener in our component for the `slotchange` event.

```javascript
class XComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(template.content.cloneNode(true));

    // get updates when content is updated in the slot
    this.shadowRoot.addEventListener('slotchange', event => console.log(event));
  }
}
```

This event will fire whenever any content has changed within the slot. To test this, we can use our component and dynamically update the content to see the event update.

```html
<x-component></x-component>

<script>
setInterval(() => {
  // update text content
  document.querySelector('x-component').textContent = `${Math.random()}`;

  // change the DOM structure
  document.querySelector('x-component').innerHTML = `<span>${Math.random()}</span>`;
}, 1000);
</script>
```

In this example, every one second, we can set the `textContent` or the `innerHTML` of the component and see the `slotchange` event fire within the `x-component`.

We can easily render content into our component templates and list for content updates. But there is one interesting exception to this rule. While the event will happen whenever textContent or innerHTML is set, the event will not occur if a `textNode` reference is updated dynamically. Let’s take a look at an example.

```html
<x-component></x-component>

<script>
const text = document.createTextNode(`${Math.random()}`);
document.querySelector('x-component').appendChild(text);
</script>
```

Instead of directly setting the `textContent` or `innerHTML` of our element we create a text node. While not an HTML element, the text node allows us to hold a reference in memory we can update at a later point. So if we go back to our interval, we will see the text change, but the event is no longer triggered.

```html
<x-component></x-component>

<script>
const text = document.createTextNode(`${Math.random()}`);
document.querySelector('x-component').appendChild(text);

setInterval(() => {
  // update text node (no slotchange update)
  text.data = `${Math.random()}`;

  // update text content (triggers slotchange update)
  // document.querySelector('x-component').textContent = `${Math.random()}`;

  // change the DOM structure (triggers slotchange update)
  // document.querySelector('x-component').innerHTML = `<span>${Math.random()}</span>`;
}, 1000);
</script>
```

This behavior can be a bit unexpected at first. Many JavaScript frameworks will leverage text nodes to optimize for performance. The short rule to remember is `slotchange` only fires when the HTML DOM has changed either by a DOM/Text Node from being added or removed. Check out the full working example below!

https://stackblitz.com/edit/lit-element-example-wgxxgx