WICG / webcomponents

Web Components specifications
Other
4.36k stars 370 forks source link

connectedCallback timing when the document parser creates custom elements #551

Closed kojiishi closed 8 years ago

kojiishi commented 8 years ago

From a blink bug, an author reported that connectedCallback is called too early in Blink.

<script>customElements.define('x-x', ...);
<script>customElements.define('y-y', ...);
<x-x>
  <div></div>
  <y-y></y-y>
</x-x>

or a sample code in jsfiddle.

When current Blink impl runs this code, this.children.length is 1 in connectedCallback for x-x. This looks like matching to the spec to me, but I appreciate discussion here to confirm if my understanding is correct, and also to confirm if this is what we should expect.

My reading goes this way:

  1. The parser is in the "in body" insertion mode.
  2. It sees x-x as Any other start tag, it insert an HTML element.
  3. insert a foreign element says to create an element for a token, then insert. Note that this "insert" is not a link, but I assume this is insert a node?
  4. In insert a node, step 5.2.1 enqueues connectedCallback.
  5. In enqueue a custom element callback reaction, it adds to the backup element queue.
  6. The parser then reads div. In its create an element for a token, definition is null, so will execute script is false. This div is then inserted.
  7. The parser then reads y-y. In its create an element for a token, definition is non-null, so will execute script is true.
  8. Step 6.2 perform a microtask checkpoint if will execute script is true, so the connectedCallback runs here.

Am I reading the spec correctly? If so, is this what we should expect?

@domenic @dominiccooney @rniwa

WebReflection commented 5 years ago

Alternatively, we could add something like finishedParsingChildrenCallback

I think nobody needs that (meaning it doesn't semantically even scale), but createdCallback from V0 would be already great, assuming it triggers as soon as the live node has been parsed so that if the CE is defined upfront, it won't trigger until the end of the node is reached, and if the CS is defined after, it triggers as soon as the browser can access its content.

Basically your solution to put an element at the end of a Custom Element to know if its ready should be backed in the Custom Element API itself, not a per-developer responsibility, since that's the moment any custom element would like to setup.

Those created procedurally via JS can trigger the same thing via tick so that adding nodes on the fly synchronously would be still possible and the component can initialize itself properly right after.

If createdCallback from V0 is a bad name due history, let it be contentParsedCallback or even readyCallback so that at least the standard would provide a universal way to setup CEs without needing mandatory ShadowDOM to be consistent (yet with same problem if there was content inside the node or not during its construction/upgrade).

rniwa commented 5 years ago

That sounds precisely like the kind of a use case MutationObserver would address.

Also, it's wrong to assume that child nodes would be inserted once and never change. Scripts can totally remove & add more child nodes later on so you'd have to have MutationObserver to observe those changes anyway.

WebReflection commented 5 years ago

That sounds precisely like the kind of a use case MutationObserver would address.

As already explained, MutationObserver doesn't trigger anything if the element is already live on the DOM and the custom element is defined after. There are example to test this. https://github.com/w3c/webcomponents/issues/551#issuecomment-429262811

Also, it's wrong to assume that child nodes would be inserted once and never change.

Nobody assumes that, we need a way to setup once the custom element. The constructor is not a good place to setup a custom element if it doesn't use shadow dom and would like to initialize or parse/understand/query/use its content.

Scripts can totally remove & add more child nodes later on so you'd have to have MutationObserver to observe those changes anyway.

You keep ignoring the issue: how to setup a custom element.

We can talk forever DOM can change, we all know this, it's a useless discussion.

Nobody knows how to setup a custom element though, in a way that works with definitions already known, loaded on demand, or procedural.

jfrazzano commented 5 years ago

It is a real problem with v1: but the compromise which changed the lovely v0 was essentially an fu to components, an effort to make CE non viable. The solution is actually Dom[0], way easier in es6, but are “naughty”. Require a monkey patch.

If child behavior depends on parent type, put a Shared function or a getter prop on HTML.prototype using Obj.defineProperty; make the getter a state machine/switch/proxy dependent on this.nodeNamr and/or this.parentElement.nodeName.

The getter on the HtmlElement.prototype will register parentsvand children immediately. There is no connected bullshit.

Here is a verbose declarative approach with animation, resize. And most of the features of flex without any css.

https://codepen.io/jfrazz/pen/yPbBQZ

rniwa commented 5 years ago

Nobody knows how to setup a custom element though, in a way that works with definitions already known, loaded on demand, or procedural.

I don't understand this. You just need to iterate over child nodes inside the constructor if there are any, schedule a MutationObserver on child node change on this and then re-iterate whenever child nodes are inserted or removed. Simple as that. The element needs to remain functional throughout this process after the constructor had finished running.

franktopel commented 5 years ago

@rniwa So if that is as simple as that, could you be so kind and post a bullet-proof boilerplate for a custom element setup? Something that just makes the component work the way a developer would naturally expect it to? Or just link me to the page that has this explained in-depth so a regular developer can understand and apply it?

More than 2 years ago, @WebReflection stated the following and I can see myself sign that:

If I might, and without sarcasm meant, the more I think about this upgrade part of the specs, the more I think it feels like "quantum physics" where it's not clear to consumers (users, developers) what a custom element is, or what it'll become. https://github.com/w3c/webcomponents/issues/551#issuecomment-242571054

I admit I haven't used MutationObserver up until now, but it feels odd that I have to, given a mature specification like web components v1, which many people have worked on for like 4 or 5 years. Doesn't this even prove there's a missing lifecycle hook?

@jfrazzano Who would ever be interested in making CEs non-viable?

WebReflection commented 5 years ago

You just need to iterate over child nodes inside the constructor ... Simple as that

Simple, right? That assumes you know inside the constructor if the Custom Elements would expect nodes to setup itself, or not.

An empty custom element is a perfectly valid use case that fails your simple approach.

A Custom Element that might be forever empty or optionally have nodes to dictate its status is another perfect common use case, i.e. my-select that would see its shape only after known its content has one opt more my-option or not, before setting up its ShadowDOM or its final shape.

How do you know when it's the time for that component to initialize itself once if not by trusting some parent has a MutationObserver to take indirectly care of that?

This is impractical, and what is missing is a way to know, from the component, the component body has been fully parsed, which should happen once for a component lifecycle, and never more than once.

Just like a constructor, with all possible setup available, including a dirty innerHTML, which instead throws arbitrary errors if the custom element is being upgraded but not fully known (childNodes.length = 0).

As simple as that.

WebReflection commented 5 years ago

P.S. @rniwa even @cramforce (Google AMP Team) had this issue forever about this ( discussed also in here https://twitter.com/cramforce/status/975310752666984448 ) and he suggested me to check for nextSibling and then again, a node could be the only child so that knowing there's no nextSibling leads to false positives.

As Malte said, we need the implementation to give developers an API to know when it's safe/OK/fine to setup a custom element (once, not per each dom mutation inside it).

edit also the browser knows this, because indeed it arbitrary throws errors if it's too early, but it doesn't expose when is not.

WebReflection commented 5 years ago

@rniwa I can't stop thinking about this

Alternatively, we could add something like finishedParsingChildrenCallback

Now, I don't care about finishedParsingChildrenCallback but I'd love to have a parsedCallback instead.

Yes, that might never happen if a Custom Element is the entire body of a huge page full of intermediate flushes, but that's a very confined problem, not the most common use case.

Basically, flushing this:

<body>
  <div>
    <my-early-definition <?php
        flush();
        usleep(300000);
      ?>
      key="value">
      <span>ear</span>
      <?php
        flush();
        usleep(300000);
      ?>
      <span>ly</span>
    </my-early-definition>
    <my-lazy-definition key="value">
      <p>lazy</p>
    </my-lazy-definition>
  </div>
</body>

It doesn't matter if there is a Custom Element or a MutationObserver, the connected will never happen until the opening tag is finished (consistently in both Chrome and Safari).

That means that when attributeChangedCallback or a mutation record with its addedNodes is triggered, the beginning of the element is known.

That flush in the middle though, will always trick the browser to early trigger either a connectedCallback or an observed mutation within added nodes.

However, what's basically impossible to know in user land but absolutely known behind the scene, is when the closing tag is either enforced or found, so that nodes found after would either be children, sibling, or part of the parent.

That is what I'd love to have in custom elements so that it is possible to understand when the element is fully known or not.

Scenarios

The parsedCallback would provide a primitive mechanism that would make any real-world Custom Element user happy because it exposes an extremely important information that is vital to understand, handle, or setup, reliably in both client and server rendered code Custom Elements.

Thanks for considering that.

cramforce commented 5 years ago

As a data point, AMP's custom element base class has a custom callback called "buildCallback" named to signal the point when custom elements should be safe to "build" there child structure.

This is currently implemented as:

For the vast majority of elements this is strictly a better time to do initialization than connectedCallback. Currently connectedCallback may be called with and without children present. That kind of racy behavior leads to bugs all over the place. Requiring use of MutationObserver for cases where actual mutations are unexpected is a really bad programming model and prone to buggy code.

Of course, some custom elements must be initialized before children are parsed. This is primarily the case for container elements that may have large amounts of child nodes and waiting for all of them to parse would break streaming rendering. This needs to continue to be supported but that isn't a good argument that there shouldn't be a shortcut for the the more common use case.

justinfagnani commented 5 years ago

I still think that a dedicated childrenChangedCallback or similar is needed to fix this very rough edge of the APIs, as requested in #550 and #619. The exact right combination of slotchange and and DOMContentLoaded events, MutationObserver, and connectedCallback, is just too obscure to be usable, and a finishedParsingChildrenCallback doesn't solve the dynamically changing children case.

The platform should provide a reasonable signal for "If I need to process children, when should I do it".

WebReflection commented 5 years ago

@justinfagnani the childrenChangedCallback is an easy peasy thing to configure in the constructor, if needed, so it's way easier to have and definitively less important than parsedCallback / buildCallback, IMO.

finishedParsingChildrenCallback doesn't solve the dynamically changing children case.

anything specific to children doesn't solve much (some component might want to inject its own children without ShadowDOM), and children are super easy to observe already.

The platform should provide a reasonable signal for "If I need to process children, when should I do it".

For one-off setup that is exactly what parsedCallback / buildCallback are being proposed for.

For anything else, if needed, we already have MutationObserver, I don't think we should slow down everything with an implicit mutation observer for children in every custom element.

franktopel commented 5 years ago

@justinfagnani The exact right combination of slotchange and and DOMContentLoaded events, MutationObserver, and connectedCallback, is just too obscure to be usable [...]

Very well agreed. Currently it appears like you got to be a total DOM lifecycle, browser DOM implementation and consider-all-the-possible-cases-guru to create robust web components relying on children. @WebReflection called it quantum physics, and that's exactly how you make regular developers resort to frameworks instead of building on top of native technologies.

Did the spec authors not see, or underestimate, or simply ignore this when designing the spec v1?

irhadkul commented 5 years ago

Hi everyone, based on the recomendations from this and other posts, we have been able to get around this problem with the folowing steps:

Edit: It was ignorant of me to present this with the words " we have been able to get around this problem" because our solution relies on the requestAnimationFrame which is going to be triggered first when the browser is the able to do so (in chromium after the DOMContentLoaded) which than means that with our solution one should not rely on the DOMContentLoaded but to build the custom "webComponentsReady" event, which delays the JS more and is bad practice. I have been able, as the @cramforce in his post suggested, to get around the problem with the readyState + nextSibling + MutationObserver. And yeah as the @WebReflection already posted, none of the solutions are going to work with the concatenated HTML. I believe that for my case this would be good enough solution but also believe that this should be solved a bit differently (template + WC ?)

Long story short (new code): See: https://github.com/w3c/webcomponents/issues/551#issuecomment-431258689

WebReflection commented 5 years ago

I'd like to underline that in a scenario like the following one, all described techniques would fail.

<!doctype html><html><head><script src="my-el.js"></script></head><body><my-el></my-el></body></html>

The my-el has no sibling and its parent neither, neither the parent parent.

Only connectedCallback would be relevant and yet it won't be granted that the end of the element has been reached so that even in this case parsedCallback / buildCallback would be needed.

WebReflection commented 5 years ago

TIL browsers beautify the content so that my-el there would have a nextSibling even if not declared, however with this content it won't, and it's still valid:

<!doctype html><html><head></head><body><my-el>
franktopel commented 5 years ago

How does all this relate to customized-built-ins that rely on children to set up, like <select is="my-select"> or <ul is="my-list">?

franktopel commented 5 years ago

So hands down, this is what we're going to give a shot, following what we were able to extract from this and other posts on the children/connectedCallback topic.

Comments welcome. Yes, we're aware that it will fail in the edge case which @WebReflection mentioned. If anyone sees any other possible edge case, please let us know.

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)
franktopel commented 5 years ago

I've put this into a public gist as well:

HTMLBaseElement

@cramforce @WebReflection Could you please take a look and comment?

franktopel commented 5 years ago

According to first performance measurements in Chrome done by my colleague @irhadkul the performance drain is minimal (single digit microseconds) when comparing the suggested method with accessing things in connectedCallback outright.

WebReflection commented 5 years ago

@franktopel things I'd do differently:

  1. use a WeakSet for the already parsed bit
  2. I wouldn't cache the parents. Nodes can be easily moved around, caching parents wouldn't give you much performance boost but it might hide shenanigans
  3. the setup should be instead the connectedCallback of the HTMLBaseElement class, and the class should also have a childrenAvailableCallback no-op (or actually ignore everything on comnnectedCallback if "childrenAvailableCallback" in this is false)
  4. the method still doesn't solve the empty custom elements case, which is not edge at all, so that if the document is loading but there won't ever be children, the mutation observer will leak forever and no childrenAvailableCallback will ever be called
  5. I usually clean up before invoking "the unknown" so the mutation observer disconnect should be the first thing to do, so that if there is an error in the childrenAvailableCallback callback it doesn't backfire forever.

Accordingly, this is how I'd go, or what makes sense to propose as standard.

const HTMLParsedElement = (() => {
  const DCL = 'DOMContentLoaded';
  const init = new WeakSet;
  const isParsed = el => {
    do {
      if (el.nextSibling)
        return true;
    } while (el = el.parentNode);
    return false;
  };
  const cleanUp = (el, observer, onDCL) => {
    observer.disconnect();
    el.ownerDocument.removeEventListener(DCL, onDCL);
    parsedCallback(el);
  };
  const parsedCallback = el => el.parsedCallback();
  return class HTMLParsedElement extends HTMLElement {
    connectedCallback() {
      if ('parsedCallback' in this && !init.has(this)) {
        init.add(this);
        if (document.readyState === 'complete' || isParsed(this))
          // ensure an order via a micro-task so that
          // parsedCallback is always after connectedCallback
          Promise.resolve(this).then(parsedCallback);
        else {
          // the need a DOMContentLoaded case
          const onDCL = () => cleanUp(this, observer, onDCL);
          this.ownerDocument.addEventListener(DCL, onDCL);
          // the early case still good to setup one
          const observer = new MutationObserver(changes => {
            changes.some(record => {
              if (record.addedNodes.length) {
                cleanUp(this, observer, onDCL);
                return true;
              }
            });
          });
          // we are interested in the element nextSibling so
          // lets observe its parent instead of its own nodes
          observer.observe(this.parentNode, {childList: true});
        }
      }
    }
  };
})();

Observing the parentNode still might hide shenanigans but at least is its only direct container and not the whole document so it's IMO most likely a more reliable approach.

Eventually, the observer could be configured as {childList: true, subtree: true} and the if should check if (isParsed(this)) instead of checking record.addedNodes.length.

WebReflection commented 5 years ago

Right ... little variation that uses a WeakMap instead and it's also observing children for all occasions

const HTMLParsedElement = (() => {
  const DCL = 'DOMContentLoaded';
  const init = new WeakMap;
  const isParsed = el => {
    do {
      if (el.nextSibling)
        return true;
    } while (el = el.parentNode);
    return false;
  };
  const cleanUp = (el, observer, ownerDocument, onDCL) => {
    observer.disconnect();
    ownerDocument.removeEventListener(DCL, onDCL);
    init.set(el, true);
    parsedCallback(el);
  };
  const parsedCallback = el => el.parsedCallback();
  return class HTMLParsedElement extends HTMLElement {
    connectedCallback() {
      if ('parsedCallback' in this && !init.has(this)) {
        const self = this;
        const {ownerDocument} = self;
        init.set(self, false);
        if (ownerDocument.readyState === 'complete' || isParsed(self))
          Promise.resolve(self).then(parsedCallback);
        else {
          const onDCL = () => cleanUp(self, observer, ownerDocument, onDCL);
          ownerDocument.addEventListener(DCL, onDCL);
          const observer = new MutationObserver(changes => {
            if (isParsed(self)) {
              cleanUp(self, observer, ownerDocument, onDCL);
              return true;
            }
          });
          observer.observe(self.parentNode, {childList: true, subtree: true});
        }
      }
    }
    get parsed() {
      return init.get(this) === true;
    }
  };
})();

I think I'll publish this one to npm.

franktopel commented 5 years ago

@WebReflection

Comments on your comments:

  1. in empty custom elements a childrenAvailableCallback, as the name implies, doesn't make much sense anyway. As far as I can see you can simply extend HTMLElement instead of HTMLBaseElement in that case. At the end of the day, this approach is meant to solve the problem that arises from children being unavailable when connectedCallback triggers.

  2. Then how would you address asynchronous adding of child elements? Probably the right approach here would be to pass a cleanUp callback to childrenAvailableCallback.

What is still open with this solution is to adjust it for use in customized-built-ins that have expectations about their children.

Btw, I was also considering publishing this on npm, but probably it'll gain more traction if you do it.

franktopel commented 5 years ago

@WebReflection I saw you're quick: https://github.com/WebReflection/html-parsed-element

I wouldn't reject attribution, neither would @irhadkul I assume :)

WebReflection commented 5 years ago

well, if any of you want I can include attributions but just to be clear, this is what HyperHTMLElement does since about ever, using a timeout instead of MutationObserver for better compatibility (down to IE9) and without guarding all calls to connected/attributeChanged before the created() is invoked.

The html-parsed-element is an alternative that might become handy for those not interested in HyperHTMLElement.

I will still link to this ticket in there so again, I don't mind adding anyone in here as contributor 👋

WebReflection commented 5 years ago

also @franktopel ...

this approach is meant to solve the problem that arises from children being unavailable when connectedCallback triggers.

My approach solves every issue. When parsedCallback is invoked you will have children in there, if any. If you want to listen to further mutations to children, just add your own Mutation Observer.

Then how would you address asynchronous adding of child elements?

You don't . You disconnect the observer too so that's not your intent and also you want to this.childrenAvailableCallback() once, and once only indeed.

Again, the missing bit that is essential is to know when it's safe to handle children or even inject nodes/html. Once we have that, everything else is trivial.

franktopel commented 5 years ago

Attribution is definitely welcome. While none of us has done anything even remotely comparable to your work, we surely contributed to the birth of html-parsed-element with the last 10 days' work. Regarding your comment for better compatibility (down to IE9) I was surprised to find HyperHTMLElement is compatible with every mobile browser and IE11 or greater on https://github.com/WebReflection/hyperHTML-Element

WebReflection commented 5 years ago

IIR you can test this page and it should work in IE9 too https://webreflection.github.io/hyperHTML-Element/test/?es5

compatibility is probably for something not fully transpilable but I don't remember what.

Feel free to file a PR for the attribution so I'm sure it's done properly/as you expect.

franktopel commented 5 years ago

Btw, how does attributeChangedCallback() behave with respect to children? We have a tabs component that is controlled via a data-active-tab attribute in combination with an attributeChangedCallback case.

WebReflection commented 5 years ago

@franktopel the attributeChangedCallback is triggered as soon as the beginning of the node is known, AKA the opening tag.

Differently from connectedCallback or whatever children watcher we want, the attributeChangedCallback will never trigger when one attribute is known but another one isn't ,because attributeChangedCallback is consistent in triggering when all attributes on the opening tag are known, instead of randomly in the wild without any signal all nodes are known, which is what my proposal addresses.

WebReflection commented 5 years ago

P.S. @franktopel attributions are live

franktopel commented 5 years ago

@WebReflection So in that case we (our team) have the exact same problem with attributeChangedCallback as well.

the attributeChangedCallback is triggered as soon as the beginning of the node is known, AKA the opening tag. How is that ever useful at all? What would a developer ever want to do at that point in time, without being able to access the element's content?

WebReflection commented 5 years ago

@franktopel it's useful for empty nodes with shadow dom, and not much else indeed.

With parsedCallback you can setup sure that attributes are there as well.

Meanwhile, if you react to attributeChangedCallback, if this.parsed is false you should queue the operations if important for the component state.

const attributeChanged = new WeakMap;
class MyEl extends HTMLParsedElement {
  parsedCallback() {
    // setup the node, you have access to all its content/attributes
    // then ...
    const changes = attributeChanged.get(this);
    if (changes) {
      attributeChanged.delete(this);
      changes.forEach(args => this.attributeChangedCallback(...args));
    }
  }

  attributeChangedCallback(...args) {
    if (!this.parsed) {
      const changes = attributeChanged.get(this) || [];
      if (changes.push(args) === 1)
        attributeChanged.set(this, changes);
      return;
    }
    // the rest of the code
  }

  connectedCallback() {
    // here you can safely add listeners
    // or set own component properties
    this.live = true;
  }

  disconnectedCallback() {
    // here you can safely remove listeners
    this.live = false;
  }
}
franktopel commented 5 years ago

Well, we're not using shadow DOM at all. We just need to replicate Swing controls for the web, for a huge migration project. And currently it needs to support IE 11 mainly. And it has to work for the next 10 to 15 years, without huge update efforts, like you'd have with using a framework like Angular or React. That's why the company decided to use native web components.

So you're saying the whole spec has been designed around a very limited use edge case?

franktopel commented 5 years ago

Also, we have been using attributeChangedCallback on a tabs component to set the initially active/visible tab, so this must again have been an issue of it working in the upgrade case, but not in the parsing case. What a mess, again!

franktopel commented 5 years ago

Wouldn't it be an even better approach to dispatch a ComponentContentLoaded custom event? That would solve the attributeChangedCallback problem as well, and it would make it so outside elements and components can attach a listener to that event (if they rely on it).

We could even have all components register at a central service in their constructor on creation, and have that same service emit a global ComponentsReady event as soon as all registered component instances have emitted their ComponentContentLoaded event.

WebReflection commented 5 years ago

You can dispatch any event you want from parsedCallback ...having hybrid callbacks and events feels inconsistent with the API , imo

Offroaders123 commented 2 years ago

https://github.com/WICG/webcomponents/issues/551#issuecomment-431258689

@franktopel and @WebReflection, thank you again for making a full implementation of this, I think it will help out a ton! I love how Web Components work, and using this in addition with Constructable Stylesheets will make everything so much more seamless, now being able to parse innerHTML consistently.

mangelozzi commented 1 year ago

Noob suggestion here, why not just link your scripts with deferred or type="module", that way you know the DOM will be parsed and the child nodes present when your connectedCallback runs?

<script type="module" src="/static/..../component.min.js>
dux commented 1 year ago

Noob suggestion here, why not just link your scripts with deferred or type="module", that way you know the DOM will be parsed and the child nodes present when your connectedCallback runs?

That will not work for nodes that are dynamically inserted, after initial setup.

dux commented 1 year ago

Solution for this problem is pretty simple, all one has to do it wrap connectedCallback in window.requestAnimationFrame and you will always have all children available.

I am pretty sure this is faster then any other proposed solution, as setTimeout # 0 and similar.

More in my answer here https://stackoverflow.com/questions/70949141/web-components-accessing-innerhtml-in-connectedcallback/75402874#75402874

Sleepful commented 1 year ago

@dux not correct apparently, read thread from here: https://github.com/WICG/webcomponents/issues/809#issuecomment-1455481698