26

我正在使用 Web 组件 v1。

假设有两个自定义元素:

父元素.html

<template id="parent-element">
    <child-element></child-element>
</template>

子元素.html

<template id="child-element">
<!-- some markup here -->
</template>

我试图在附加时使用connectedCallbackinparent-element来初始化整个父/子 DOM 结构,这需要与child-element.

但是,在被解雇时似乎child-element没有正确定义:connectedCallbackcustomElement

父元素.js

class parent_element extends HTMLElement {
    connectedCallback() {
        //shadow root created from template in constructor previously
        var el = this.shadow_root.querySelector("child-element");
        el.my_method();
    }
}

这是行不通的,因为el是 aHTMLElement而不是 a child-element

parent-element一旦正确附加了其模板中的所有子自定义元素,我需要一个回调。

这个问题的解决方案似乎不起作用;this.parentElementnull里面child-element connectedCallback()

伊利蒙

4

6 回答 6

15

在 ShadowDOM 模板中使用插槽元素。

以某种方式构建您的自定义元素,以便它们可以存在于任何上下文中,例如作为子元素或父元素,而不与其他自定义元素有任何依赖关系。这种方法将为您提供模块化设计,您可以在任何情况下使用您的自定义元素。

但是,只要有子元素存在,您仍然想做一些事情,例如选择它们或调用子元素的方法。

插槽元素

为了解决这个<slot>问题,引入了元素。使用插槽元素,您可以在 ShadowDOM 模板中创建占位符。这些占位符可以通过简单地将一个元素作为 DOM 中的子元素放置在您的自定义元素中来使用。然后子元素将被放置在放置<slot>元素的位置内。

但是你怎么知道一个占位符是否已经被一个元素填充了呢?

插槽元素可以侦听一个名为 的独特事件slotchange。只要将一个元素(或多个元素)放置在元素的位置上,就会触发此事件slot

在事件的侦听器中,您可以使用HTMLSlotElement.assignedNodes()orHTMLSlotElement.assignedElements()方法访问占位符中的所有元素。这些返回一个数组,其中的元素放置在slot.

现在您可以等待孩子被放置在插槽内,然后对在场的孩子做一些事情。

这种方式允许您只操作 DOM,而让 ShadowDOM 不理会,让它自己工作。就像您对常规 HTML 元素所做的那样。

事件会等待所有子元素连接吗?

是的,在调用自定义元素的slotchange所有方法后触发该事件。connectedCallback这意味着在收听赛事时没有赛车条件或缺少设置。

class ParentElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `
      <h2>Parent Element</h2>
      <slot></slot>
    `;
    console.log("I'm a parent and have slots.");
    
    // Select the slot element from the ShadowDOM..
    const slot = this.shadowRoot.querySelector('slot');
    
    // ..and listen for the slotchange event.
    slot.addEventListener('slotchange', (event) => {
      // Get the elements assigned to the slot..
      const children = event.target.assignedElements();
      
      // ..loop over them and call their methods.
      children.forEach(child => {
        if (child.tagName.toLowerCase() === 'child-element') {
          child.shout()
        }
      });
    });
  }
  
  connectedCallback() {
    console.log("I'm a parent and am now connected");
  }
}

customElements.define('parent-element', ParentElement);

class ChildElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `
      <h3>Child Element</h3>
    `;
  }
  
  connectedCallback() {
    console.log("I'm a child and am now connected.");
  }

  shout() {
    console.log("I'm a child and placed inside a slot.");
  }

}

customElements.define('child-element', ChildElement);
<parent-element>
  <child-element></child-element>
  <child-element></child-element>
  <child-element></child-element>
</parent-element>

于 2019-10-12T14:10:32.337 回答
6

connectedCallback在其任何自定义元素子元素升级之前,第一次调用它存在时间问题。<child-element>在被调用时只是一个 HTMLElement connectedCallback

要获取升级后的子元素,您需要在超时内完成。

运行下面的代码并观察控制台输出。当我们尝试调用孩子的方法时,它失败了。同样,这是因为 Web 组件的创建方式。以及何时connectedCallback调用的时间。

但是,在setTimeout调用子方法的过程中有效。这是因为您允许子元素有时间升级到您的自定义元素。

如果你问我,有点愚蠢。我希望在所有孩子都升级后调用另一个函数。但我们用我们拥有的东西工作。

class ParentElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = '<h2>Parent Element</h2><child-element></child-element>';
  }
  
  connectedCallback() {
    let el = this.shadowRoot.querySelector("child-element");
    console.log('connectedCallback', el);
    try {
      el.childMethod();
    }
    catch(ex) {
      console.error('Child element not there yet.', ex.message);
    }
    setTimeout(() => {
      let el = this.shadowRoot.querySelector("child-element");
      console.log('setTimeout', el);
      el.childMethod();
    });
  }
}

customElements.define('parent-element', ParentElement);


class ChildElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = '<h3>Child Element</h3>';
  }

  childMethod() {
    console.info('In Child method');
  }
}

customElements.define('child-element', ChildElement);
<parent-element></parent-element>

于 2018-02-14T00:13:13.110 回答
4

经过更多的工作,我有了某种解决方案。

当然this.parentElement在子元素中不起作用;它位于 shadow DOM 的根目录中!

我目前的解决方案,适用于我的具体场景,如下:

父元素.js

init() {
    //Code to run on initialisation goes here
    this.shadow_root.querySelector("child-element").my_method();
}

子元素.js

connectedCallback() {
    this.getRootNode().host.init();
}

因此,在子元素中,我们获取根节点(模板影子 DOM),然后是它的宿主、父元素和 call init(...),此时父元素可以访问子元素并且它是完全定义的。

由于几个原因,此解决方案并不理想,因此我没有将其标记为已接受。

1) 如果有多个子等待,或者更深的嵌套,协调回调会变得更加复杂。

2)我担心对 的影响child-element,如果我想以独立的能力使用这个元素(即在其他地方,完全独立于嵌套parent-element),我将不得不修改它以明确检查是否getRootNode().hostparent-element.

所以这个解决方案目前有效,但感觉很糟糕,我认为当父级的整个 DOM 结构(包括其影子 DOM 中的嵌套自定义元素)被初始化时,需要在父级上触发一个回调。

于 2018-02-07T13:00:50.037 回答
2

如果您想避免由于 setTimeout 的延迟而导致任何视觉故障,您可以使用MutationObserver

class myWebComponent extends HTMLElement 
{
      connectedCallback() {

        let childrenConnectedCallback = () => {
            let addedNode = this.childNodes[(this.childNodes.length - 1)];
            //callback here
        }

        let observer = new MutationObserver(childrenConnectedCallback);
        let config = { attributes: false, childList: true, subtree: true };
        observer.observe(this, config);

        //make sure to disconnect
        setTimeout(() => {
            observer.disconnect();
        }, 0);

     }
}
于 2018-12-12T16:44:53.973 回答
0

我们遇到了非常相关的问题,connectedCallback我们的自定义元素 (v1) 中的子元素不可用。

起初,我们尝试connectedCallback使用一种非常复杂的方法来解决这个问题,谷歌 AMP 团队也在使用这种方法(结合mutationObserver并检查nextSibling),最终导致https://github.com/WebReflection/html-parsed-element

不幸的是,这产生了它自己的问题,因此我们回到始终强制执行升级案例(即,包括仅在页面末尾注册自定义元素的脚本)。

于 2019-02-26T15:45:31.587 回答
-1
document.addEventListener('DOMContentLoaded', defineMyCustomElements);

您可以延迟定义您的类,直到加载 dom 之后。

于 2018-10-04T15:24:01.597 回答