textContent为空的自定义HTMLElement的connectedCallback()

时间:2018-01-29 10:00:34

标签: javascript web web-component custom-element native-web-component

在我的自定义元素的connectedCallback()方法中,textContent作为空字符串返回。

基本上我的代码归结为以下内容......

class MyComponent extends HTMLElement{
    constructor() {
        super()

        console.log(this.textContent) // not available here, but understandable
    }           

    connectedCallback() {
        super.connectedCallback() // makes no difference if present or not

        console.log(this.textContent) // not available here either, but why?!
    }
}

customElements.define('my-component', MyComponent);     

HTML ...

<my-component>This is the content I need to access</my-component>

从阅读connectedCallback()开始,一旦将元素添加到DOM中,就会调用它,因此我希望textContent属性应该有效。

如果有帮助,我正在使用Chrome 63

3 个答案:

答案 0 :(得分:2)

您面临的问题与我们的团队在当前项目中遇到的问题基本相同:

connectedCallback in Chrome does not guarantee children are parsed.具体来说,在 upgrade 情况下,依赖子项是有效的,但是如果在浏览器解析该元素时预先知道该元素,则不起作用。因此,如果将webcomponents.js包放置在body的末尾,它至少可以可靠地用于您之前拥有的静态文档(但是如果您在{{ 1}})。这基本上就是您发布的解决方案。

更糟糕的是,在“自定义元素”规范v1中没有生命周期挂钩可以保证子元素的访问。

因此,如果您的自定义元素依赖于子级来设置(而像DOMContentLoaded 这样的简单textNode就是子级节点),那么我们就是经过一周的过度研究和测试(which is what the Google AMP team does as well)后可以提取:

textContent
class HTMLBaseElement extends HTMLElement {
  constructor(...args) {
    const self = super(...args)
    self.parsed = false // guard to make it easy to do certain stuff only once
    self.parentNodes = []
    return self
  }

  setup() {
    // collect the parentNodes
    let el = this;
    while (el.parentNode) {
      el = el.parentNode
      this.parentNodes.push(el)
    }
    // check if the parser has already passed the end tag of the component
    // in which case this element, or one of its parents, should have a nextSibling
    // if not (no whitespace at all between tags and no nextElementSiblings either)
    // resort to DOMContentLoaded or load having triggered
    if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
      this.childrenAvailableCallback();
    } else {
      this.mutationObserver = new MutationObserver(() => {
        if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
          this.childrenAvailableCallback()
          this.mutationObserver.disconnect()
        }
      });

      this.mutationObserver.observe(this, {childList: true});
    }
  }
}

class MyComponent extends HTMLBaseElement {
  constructor(...args) {
    const self = super(...args)
    return self
  }

  connectedCallback() {
    // when connectedCallback has fired, call super.setup()
    // which will determine when it is safe to call childrenAvailableCallback()
    super.setup()
  }

  childrenAvailableCallback() {
    // this is where you do your setup that relies on child access
    console.log(this.innerHTML)
    
    // when setup is done, make this information accessible to the element
    this.parsed = true
    // this is useful e.g. to only ever attach event listeners to child
    // elements once using this as a guard
  }
}

customElements.define('my-component', MyComponent)

答案 1 :(得分:1)

我设法通过在DOMContentLoaded事件被触发后仅调用customElements.define('my-component', MyComponent);来解决这个问题。

document.addEventListener('DOMContentLoaded', function() {
    customElements.define('my-component', MyComponent);   
}

这种行为似乎有点奇怪,因为你期望connectedCallback只有在节点插入DOM并且完全准备好被操作后才会触发。

答案 2 :(得分:1)

您可以使用插槽和slotchange事件(插槽获取主机标签内容)来访问内容。

(function(){
    
    class MyComponent extends HTMLElement {
        
        constructor() {
            super();
            
            let slot = document.createElement('slot') ;

            slot.addEventListener('slotchange', function(e) {
                let nodes = slot.assignedNodes();
                console.log('host text: ',nodes[0].nodeValue);                               
            });
  
            const shadowRoot = this.attachShadow({mode: 'open'});
            shadowRoot.appendChild(slot);     
        }
        
    }
            
    window.customElements.define('my-component', MyComponent);
})();
<my-component>This is the content I need to access</my-component>