Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some sort of guarantee on custom element lifecycle order of events #737

Closed
trusktr opened this issue Feb 18, 2018 · 5 comments
Closed

Some sort of guarantee on custom element lifecycle order of events #737

trusktr opened this issue Feb 18, 2018 · 5 comments

Comments

@trusktr
Copy link
Contributor

trusktr commented Feb 18, 2018

It'd be nice to have some sort of guarantee on the order in which certain methods like connectedCallback are fired during parsing of HTML, and in other cases.

The problem

In #559, I have some problems, which ultimately stem from the fact that the order in which elements are upgraded in the DOM (breadth-first vs depth-first) depends on whether upgrade happens

  1. during parsing (i.e. elements were defined using customElements.define() in a <script> tag in the <head>, before the <body> is parsed)
  2. or after the HTML is parsed (f.e. elements are defined in a <script> tag at the end of the <body> so that by the time customElements.define() is called, the DOM already exists)

Either of the cases apply at other points in runtime too:

  1. The first case also applies in any case at runtime and after DOM is ready, where customElements.define() is used to define an element then after that the respective type of element is created and appended into DOM.
  2. The second case also applies in any case at runtime and after DOM is ready, where an element is first created and appended into the DOM then it is defined with customElements.define()

Here is a demonstration of one form of each case (though other forms exist):

https://jsfiddle.net/wkwmzLwL

The order of upgrade is reversed between those two cases.

Why is this bad?

This leads to ugly conditional checking and/or deferral hacks needed in order for parent elements to be able to always work with child elements where the child elements are expected to be certain types of custom elements. This can lead to harder-to-maintain code and increases surface area for unexpected and weird bugs.

Here's an example of a hack that solves the problem from the previous fiddle:

https://jsfiddle.net/aqp4uaja

Solution?

I'm not quite sure what a solution (for browsers to implement) would be exactly. Some solution ideas:

  1. Maybe [idea] childConnectedCallback and childDisconnectedCallback #550 could help in some cases:
  • For example, suppose that during parsing, childConnectedCallbacks are only called after child elements are upgraded iff the child elements have already been defined by customElements.define.
  • If the child elements' names are not yet defined in customElements, then it doesn't make sense to wait for upgrades that may never happen. In this case, I'm not sure what's a clear solution.
  1. Another possibility could be to forbid elements from being defined after they're already in the DOM. F.e.:
  • DOMException: An element named 'some-thing' has already been created. Elements should be defined before they are created.
  1. Maybe upgrade elements in depth-first order
  • This doesn't seem like the best idea because then we'll have many HTMLUnknownElements to upgrade, which can cost more CPU.
  1. In contrast to point 3, maybe instantiate custom elements like we do now during parsing in breadth-first order, but wait for a closing tag of a custom element to be encountered before calling it's connectedCallback, which makes it possible to ensure that child element connectedCallbacks are called first, in depth-first order.
  • The only problem with this is that if the HTML payload is huge, it may take a long time (due to network) before elements come to life. This problem also applies to point 3.
    • Personally, I'm fine with this. I'd rather add a nice loader animation, and break down my web app to small pieces (f.e. a huge table can be paginated so that load time is bearable, loading a page of 10 items instead of 1000 items for example).

Basically, some consistency would be great: it would lend to cleaner and more solid Web Components with less hacks and less surface area for bugs.

@trusktr trusktr changed the title Some sort of guarantee on upgrade order? Some sort of guarantee on custom element lifecycle order of events Feb 18, 2018
@annevk
Copy link
Collaborator

annevk commented Feb 18, 2018

This is by design. So you design robust components that can be created imperatively.

@Jamesernator
Copy link

You said that

whenDefined() only works when the library author knows the names that library constructors will be assigned to

Given that constructors can only be used in a custom element registry once (effectively making the registry a Bi-Map), would it work for your use cases to have a method like .whenRegistered(CustomElementConstructor) that is resolved to the custom element name its associated with?

Then you could just use it like this:

class ElementOne extends HTMLElement {
    async connectedCallback() {
        await customElements.whenRegistered(ElementTwo)
    }
}

@domenic
Copy link
Collaborator

domenic commented Feb 21, 2018

I don't see a lot of actionable work here; as noted by @annevk this is working as designed. Shall we close?

@trusktr
Copy link
Contributor Author

trusktr commented Feb 22, 2018

This is by design. So you design robust components that can be created imperatively.

This is mostly a problem when creating them declaratively: they start in HTML, then parsed, and depending on various factors, children (of an element with child-observing logic) may or may not be upgraded inside of (for example) the observing-element's connectedCallback invocation.

When I create elements imperatively, this problem doesn't exist in my cases.

.whenRegistered(CustomElementConstructor)

That would be helpful, then we don't need to care what the elements are named! Something like .get(Constructor) could help too (returns string or undefined) to avoid async when possible:

if (!customElements.get(SomeElementConstructor))
  await customElements.whenDefined(SomeElementConstructor)

@annevk
Copy link
Collaborator

annevk commented Feb 22, 2018

That is already tracked by #566. I'm closing this since not being able to depend on your children being ready is by design. You'll need to use mutation observers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants