Причиной будет то, что connectedCallback()
пользовательского элемента в некоторых случаях будет вызываться, как только браузер встретит открывающий тег пользовательского элемента, , когда дочерние элементы не анализируются, и, таким образом, недоступно . Это делает, например, это происходит в Chrome, если вы определяете элементы заранее, а браузер затем анализирует HTML.
Вот почему let inputs = this.getElementsByTagName('spk-input')
в вашем update()
методе внешнего <spk-root>
не может найти никаких элементов. Не дайте себя одурачить, введя туда неверный вывод console.log.
Я только недавно глубоко погрузился в эту тему и предложил решение с использованием класса HTMLBaseElement
:
https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7
Андреа Джаммарки (автор document-register-element
polyfill для пользовательских элементов в неподдерживающих браузерах) воспользовался этим решением и создал из него пакет npm:
https://github.com/WebReflection/html-parsed-element
Пока вам не нужно динамическое создание пользовательских элементов, самое простое и надежное решение - создать сценарий upgrade , поместив сценарии определения вашего элемента в конце body
. .
Если вам интересно обсуждение темы (долго читайте!):
https://github.com/w3c/webcomponents/issues/551
Вот полный смысл:
Класс HTMLBaseElement, решающий проблему вызова connectedCallback до разбора потомков
Существует огромная практическая проблема с веб-компонентами spec v1:
В некоторых случаях connectedCallback
вызывается, когда дочерние узлы элемента еще не доступны.
Это делает веб-компоненты неработоспособными в тех случаях, когда они полагаются на своих детей при настройке.
См. https://github.com/w3c/webcomponents/issues/551 для справки.
Чтобы решить эту проблему, мы создали в нашей команде класс HTMLBaseElement
, который служит новым классом для расширения автономных пользовательских элементов.
HTMLBaseElement
, в свою очередь, наследует от HTMLElement
(из каких автономных пользовательских элементов должен происходить какой-то момент в их цепочке прототипов).
HTMLBaseElement
добавляет две вещи:
-
setup
метод, который заботится о правильной синхронизации (то есть гарантирует, что дочерние узлы доступны), а затем вызывает childrenAvailableCallback()
на экземпляре компонента.
- a
parsed
Логическое свойство, которое по умолчанию равно false
и должно быть установлено в true
, когда начальная настройка компонентов будет выполнена. Это должно служить в качестве охраны, чтобы убедиться, например, слушатели дочерних событий никогда не подключаются более одного раза.
HTMLBaseElement
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 once
// to child element nodes using this as a guard
}
}