Search code examples
javascriptweb-componentlit-element

Lit-Element: which event to use for DOM updates?


The documentation over at github.com/Polymer/lit-element describes the lifecycl, if a property of some lit-element is changed. However, I can not seem to find any documentation about a lifecycle if the DOM content of the element is changed.

So assume I have some nested DOM structure and my outermost element should display something based on the DOM content. For sake of simplicity the example below will just display the number of child-elements of the given type.

Now at some point my application inserts a new nested element (click the test button below). At this point I would like to update the shown count.

From my tests it seems that render() is not called again in that case, neither is updated().

Which event do I need to listen or which function do I need to implement for to recognize such a change?

My only current workaround is to use requestUpdate() manually after the DOM update, but I think such changes should be handled by lit-element itself.

  document.querySelector( 'button' )
          .addEventListener( 'click', () => {
            const el = document.querySelector( 'my-element' );
            el.insertAdjacentHTML( 'beforeend', '<my-nested-element>new addition</my-nested-element>' );
          })
my-element, my-nested-element {
  display: block;
}
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@latest/webcomponents-loader.js"></script>

<!-- Works only on browsers that support Javascript modules like Chrome, Safari, Firefox 60, Edge 17 -->
<script type="module">
  import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element/lit-element.js?module';

  class MyElement extends LitElement {
    constructor(){
      super();
      this.number = this.querySelectorAll( 'my-nested-element' ).length;
    }
  
    render() {
      return html`<p>number of my-nested-element: ${this.number}</p> 
                  <slot></slot>`;
    }  
  }
  customElements.define('my-element', MyElement);
  
	class MyNestedElement extends LitElement {
    render() {
      return html`<slot></slot>`;
    }  
  }
  customElements.define('my-nested-element', MyNestedElement);
</script>

<my-element>
  <my-nested-element>first</my-nested-element>
  <my-nested-element>second</my-nested-element>
</my-element>

<button>test</button>


Solution

  • In order to detect a new element inserted from the Light DOM through a <slot> element, you can listen to slotchange events on the <slot> element, or on the Shadow DOM root itself.

    See the running example below:

    document.querySelector('button').onclick = () => 
      document.querySelector('my-element').insertAdjacentHTML('beforeend', '<my-nested-element>new addition</my-nested-element>');
      
    my-element,
    my-nested-element {
      display: block;
    }
    <script type="module">
      import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element/lit-element.js?module';
    
      class MyElement extends LitElement {
        firstUpdated() {
          var shadow = this.shadowRoot
          var nb = shadow.querySelector( 'span#nb' )
          shadow.addEventListener( 'slotchange', () => 
            nb.textContent = this.querySelectorAll( 'my-nested-element').length 
          )    
        }
        render() {
          return html`<p>number of my-nested-element: <span id="nb"></span></p> 
                      <slot></slot>`;
        }  
      }
      customElements.define('my-element', MyElement);
    </script>
    
    <my-element>
      <my-nested-element>first</my-nested-element>
      <my-nested-element>second</my-nested-element>
    </my-element>
    
    <button>test</button>