1

I have looked at a number of examples (eg this one) of this which don't work for me.

A simple Lit component. I would like to add an event listener on the button, selected by its id. The result in the console is null - it can't find the button in the shaddow Dom. What am I doing wrong?

import { html, LitElement } from "lit";

class MyElement extends LitElement {

  connectedCallback() {
    super.connectedCallback();
    var buttonElement = this.shadowRoot.getElementById("myButton");
    console.log(buttonElement);
  }

  render() {
    return html` <button id="myButton">Click</button>`;
  }
}

customElements.define("my-element", MyElement);

UPDATE:

Putting it here worked.

firstUpdated() {
  var buttonElement= this.shadowRoot.getElementById("myButton");
  console.log(buttonElement);
}
RGriffiths
  • 5,722
  • 18
  • 72
  • 120
  • Why do you need to select it via id? Would ref help in your case, which is imho much more elegant? – Christian Jul 26 '23 at 12:46
  • It is just an example. The type of selector is not the issue. However it seems that the shadow dom is not fully rendered at the point of connectedCallback(). Putting it in firstUpdated() worked. – RGriffiths Jul 26 '23 at 12:55
  • I was just compiling an example with firstUpdated, while you were answering. – Christian Jul 26 '23 at 13:03
  • 1
    Side note, for cases like this prefer binding the event declaratively with `@`. For a `click` event use: `@click=${}`. If it's a custom event `my-custom-evt`, then `@my-custom-evt` works. Reference: https://lit.dev/docs/components/events/#adding-event-listeners-in-the-element-template Lit handles adding and cleaning up events in this case. – YouCodeThings Jul 26 '23 at 15:46
  • 1
    @YouCodeThings Thanks. Yes, that is another way. It is just that project I am working on uses listeners and I was trying to understand the logic. – RGriffiths Jul 27 '23 at 15:09

2 Answers2

0

Please use ref if possible. Otherwise you could use firstUpdated. Also you should use renderRoot instead of shadowRoot, because it's not said, that the renderRoot is always a shadowRoot. See https://lit.dev/docs/components/shadow-dom/

<script type="module">
import {
  LitElement,
  html
} from "https://unpkg.com/lit-element/lit-element.js?module";
import {
  createRef, ref
} from "https://unpkg.com/lit/directives/ref.js?module";

class MyElement extends LitElement {

  buttonRef = createRef();

  firstUpdated() {
    super.firstUpdated();
    const buttonElement = this.renderRoot.querySelector("#myButton");
    console.log(buttonElement);
    console.log(this.buttonRef.value);
  }

  render() {
    return html` <button id="myButton" ${ref(this.buttonRef)}>Click</button>`;
  }
}

customElements.define("my-element", MyElement);
</script>
<my-element></my-element>
Christian
  • 3,503
  • 1
  • 26
  • 47
0

shadowDOM, created in the constructor is available in the connectedCallback

But in Lit render filling your shadowDOM (again) runs (too) late, after the connectedCallback

customElements.define("my-component", class extends HTMLElement {
  constructor() {
    super()
      .attachShadow({mode: "open"})
      .innerHTML = `<button>CLICK</button>`;
  }
  connectedCallback() {
    this
      .shadowRoot
      .querySelector("button")
      .onclick = evt => alert(evt.type +": "+ evt.target.nodeName)
  }
})
<my-component></my-component>

Note: since its targeting shadowDOM and not DOM, you could set the click handler immediately in the constructor

Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49