Search code examples
javascriptgoogle-chromeweb-componentshadow-domcustom-element

How to access anchestor of nested web components?


I'm implementing an Orchestrator pattern for my web components, like this:

<body>
  <my-controller>
    <div>
      <my-list>
        <span>
          <my-item></my-item>
        </span>
      </my-list>
    </div>
  </my-controller>
</body>

All custom elements I created utilize Shadow DOM using const root = super.attachShadow({mode: "open"}); root.appendChild(...);.

From my inner web components I want to reach my my-controller component in connectedCallback():

public connectedCallback(): void
    {
        if (this.isConnected)
        {
            for (let node = this.parentElement; node; node = node.parentElement)
                if (node instanceof ContainerBase)
                {
                    this._service = (<ContainerBase>node).GetService(this);
                    break;
                }

            if (this._service) this.Reset();
            else throw new ReferenceError(`${this.nodeName.toLowerCase()}: Couldn't find host element while connecting to document.`);
        }
    }


The strange thing is: I can only reach the immediate parent web control.


So, if connectedCallback() is called on <my-list> I can reach <my-controller>, but if connectedCallback() is called on <my-item> I only reach <span>. I can't even reach <my-list> when I'm starting my search with <my-item>.

Even when I walk the DOM tree after connectedCallback() is called, I cannot reach beyond <span> when I start at <my-item>.

Is this by intention?

Why can I reach an outer web component from the first nested one while I cannot reach the first nested web component from the second nested one?

How can I go up the DOM tree completely, from any nested level?


Solution

  • When you define a Custom Element content whith a Shadow DOM, you create a distinct DOM tree. The Shadow DOM is a DocumentFragment with no root element.

    As a consequence, you cannot reach its (intuitive) ancestor simply by walking the DOM up by the parentElement property.

    To reach the host element of a Shadow DOM, instead use getRootNode() combined with host.

    From <my-item>'s connectedCallback() method:

    connectedCallback() {
       var parent = this.getRootNode().host
       console.log( parent.localNode ) // my-list
    }
    

    If you want to get an ancestor, you could try this recursive function.