Search code examples
javascriptcallbackprototypelifecyclecustom-element

Defining the custom-element's lifecycle callbacks inside of the constructor


The following defines, creates and finally inserts an instance of my "autonomous custom element" onto the container:

class MyCustomElement extends HTMLElement {
  static get elementName() {
    return 'my-custom-element';
  }

  constructor() {
    const self = super();
    let _myPrivateData = 'privateValue';

    // the following does not cause the callback to be invoked at all!
    MyCustomElement.prototype.connectedCallback = function() {
      console.log('connected!');
    };

    return self;
  }

  //// this would've got invoked anyways, but there's no access to the `_myPrivateData`
  // connectedCallback() {
  //     console.log('connected!');
  // }
}

let myCustomElementName = MyCustomElement.elementName;
customElements.define(myCustomElementName, MyCustomElement);

let myCustomElement = document.createElement(myCustomElementName);
let container = document.getElementById('container');
container.appendChild(myCustomElement);
<div id="container"></div>

I've defined the connectedCallback inside of the "constructor" to have access to _myPrivateData, but then the callback does not get invoked at all! Having the identical code-excerpt immediately after the "class body" does cause the callback to be invoked in expense of not having access to _myPrivateData.

Why is that? How shall I approach the issue to have access to _myPrivateData (preserving its "private" nature) and have the callback invocation working?


Solution

  • Because of the Custom Element implementation you must define all the callbacks before the elemennt is defined with customElements.define(). These callbacks are readonly.

    To deal with private values, you could define you own custom callback that would be invoked by the standard connectedCallback() method.

    See the example below:

    class MyCustomElement extends HTMLElement {
      constructor() {
        super()
        let _myPrivateData = 'privateValue'
    
        // the following does not cause the callback to be invoked at all!
        this.connectedCallback = function() {
          console.log('connected! : ' + _myPrivateData )
        }
      }
    
      connectedCallback() {
          this.connectedCallback()
      }
    }
    
    customElements.define('my-custom-element', MyCustomElement)
    <my-custom-element></my-custom-element>

    Update

    Effective lifecycle callbacks are actually readonly / frozen. You cannot modify them once the custom element is defined.

    Consider that, when you call customElements.define(), the lifecycles callbacks are copied to the Custom Element Registry, from the class callback entries with the same name, or set to void() if the callback name doesn't exist.

    When an instance of the custom element is created, these are the callback copies that are called, not the callback class prototype methods. You cannot access and therefore modify these copies.

    You can still modifiy the class callbacks (the prototype entries) but it won't affect the custom element lifecyle.

    The will enforce the consistancy of the life cycle for all the same custom element instances.