web-componentlit-elementlit-html

lit-element getElementById in connectedCallback returns null


I remember many web components tutorials say connectedCallback is good for hooking up DOM events or getting some DOM elements (like this one). But I couldn't get a simple program to run in lit-element:

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  render() {
    return html`<p id="root">template content</p>`;
  }

  connectedCallback() {
    super.connectedCallback();
    this.shadowRoot.getElementById('root'); // null
  }

  firstUpdated() {
    this.shadowRoot.getElementById('root'); // not null
  }
}

Looks like it only works in firstUpdated, doesn't firstUpdate fire after first render? what if I need some events set up before first render? why connectedCallback doesn't work here?


Solution

  • connectedCallback works according to the spec, it just doesn't do what you expect.

    Those callbacks fire at different moments in time

    <my-element myattribute="foo">   1. -> connectedCallback
      <p id="root">
        template content
      </p>
    </my-element>  2. -> firstUpdated (LitElement)
    

    1. -> connectedCallback means the Element is injected in the DOM. but its children are not parsed yet.

    Unless you use a FireFox (pre 2021 version!!), which fires connectedCallback too late at 2. (confirmed bug)

    Think of elements as water hoses; connect means the hose is connected to a tap, not that water flows through it. So you can access data-attributes (a property of the hose) in the connectedCallback

    That is why the connectedCallback fires at this 1. -> moment in time... you may want content based on its attributes. And you don't want to wait for all content to be parsed (which potentially can be a very very large DOM tree)

    Also note connectedCallback runs every time you move the Element in the DOM

    And if you dive deeper you will also notice attributeChangedCallback runs before the connectedCallback if you have observerAttributes on your element.

    LitElement lifecycle callbacks are sugar, they 'safeguard' you against (powerful) bare metal Web Component behaviour.

    So without LitElement, you can do:

    connectedCallback(){
      // 1. -> do stuff
      setTimeout(() => {//no tricks, just wait till that dreaded JS Event Loop is done
        // all children are parsed
        // 2. -> do stuff
      });
    }
    

    For gory callback details see: wait for Element Upgrade in connectedCallback: FireFox and Chromium differences