Home
Head's Up: I'm in the middle of upgrading my site. Most things are in place, but there are something missing and/or broken including image alt text. Please bear with me while I'm getting things fixed.

Trigger An Action When Something Changes A Web Component's Content Externally

I'm working on a code block with auto copy web component 1 for my color picker 2 . The content needs to be able to be updated from external javascript. It's possible to use slots and simply have the content update, but I'm running a process to add line numbers so I need an alternative.

The approach I'm using here is to have a mutation observer to watch for changes and then trigger an update.

For this example, the button is outside the web component. It updates the custom element's span which is what's being observed by the component's code. When a change happens, the value of the span is used to generate a new value that's then output into the shadowRoot.

Here's a live example :

Original Text

JavaScript

customElements.define(
  'wc-example',
  class CodeBlock extends HTMLElement {
    constructor() {
      super()
      this.attachShadow({ mode: 'open' })
    }

    connectedCallback() {
      this.content = this.template().content.cloneNode(true)
      this.shadowRoot.appendChild(this.content)
      const observer = new MutationObserver((list, observer) => {
          this.updateContent.call(this, list, observer)
      });
      observer.observe(this, {childList: true, subtree: true,})
    }

    template() {
      const template = this.ownerDocument.createElement('template')
      template.innerHTML = `
        <div class="output-text" data-count="0"></div>
      `.trim()
      return template
    }

    updateContent(list, observer) {
      const inputText = 
          this.querySelector('.input-text')
      const outputText = 
          this.shadowRoot.querySelector('.output-text')
      outputText.dataset.count = 
          parseInt(outputText.dataset.count, 10) + 1
      outputText.innerHTML = 
          `${inputText.innerHTML} (${outputText.dataset.count})`
    }

    triggerUpdate(list, observer) {
      this.updateContent()
    }

  }
)

JavaScript

document.addEventListener("DOMContentLoaded", () => {
  const button = document.querySelector('.external-button')
  button.addEventListener('click', () => {
    const slot = document.querySelector('.input-text')
    slot.innerHTML = 'External Click Text Update'
  })
})

The mutation observer fires once when things are set up. That threw me off a little. I was expecting things wouldn't change by the point the observer is started. That feels like it may be a race condition. Something to look into there.

Using slots directly is probably the way to go if it works for the use case. This is working nicely for the updates that as far as I can tell slots don't cover without more work.

I'm using the MutationObserver here. There's also the slotchange event . Using it requires adding and removing attributes thought which seems more complicated

Footnotes And References