-
Notifications
You must be signed in to change notification settings - Fork 375
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
[idea] childConnectedCallback and childDisconnectedCallback #550
Comments
In v0 API, there was |
it's also an overlap with |
|
well, it could but that's undesired, hence my suggestion to use |
Actually, I'm running into hitches due to this, which makes it difficult to coordinate some of my connected/disconnected logic with my polyfill. For reference, my polyfill looks like the following in my // some code omitted for brevity...
export default
function WebComponentMixin(elementClass) {
if (!elementClass) elementClass = HTMLElement
// some code omitted for brevity...
class WebComponent extends elementClass {
// constructor() is used in v1 Custom Elements instead of
// createdCallback() as in v0.
constructor() {
super()
// some code omitted for brevity...
this.createdCallback()
}
createdCallback() {
// some code omitted for brevity...
const observer = new MutationObserver(changes => {
for (let change of changes) {
if (change.type != 'childList') continue
for (let node of change.addedNodes)
this.childConnectedCallback(node)
for (let node of change.removedNodes)
this.childDisconnectedCallback(node)
}
})
observer.observe(this, { childList: true })
}
// Subclasses can implement these.
childConnectedCallback(child) {}
childDisconnectedCallback(child) {}
// some code omitted for brevity...
}
// some code omitted for brevity...
return WebComponent
} Then, subclasses simply implement the |
Updated my previous comment, meant to say
|
IMO, it'd be nice that (if this feature existed) that This would allow parents to set up certain things (for example pass a message or state to a child), then allow for the child to react to the message or state in Similar with Currently, my library just silently fails, which is not ideal, and I definitely don't want to introduce polling, and introducing async coordination with promises complicates things a ton, even if using |
Idea from #559 (comment): callbacks for different types of changes:
|
I agree with @WebReflection here, I don't think asking for child*Callback is realistic, specially because of its perf characteristics, which will probably force us to do something similar to the |
There's no interest in tackling callbacks for children alone. This would be considered if someone came up with an imperative API for slotting (note: thus far we haven't been able to do so). |
I also just ran into this issue and put together an example. In my example, the MutationObserver only handles the case where children are added, hopefully it's self evident how to handle the removal case. I process any existing children in my connectedCallback, and from then on rely on the MutationObserver for any changes. Would the folks on this thread mind taking a look and let me know if there are any issues with this approach? https://stackblitz.com/edit/custom-element-mo?file=index.html |
@robdodson That's more or less what I've started doing, but you need to disconnect on |
@calebdwilliams good point, I'll add that! |
By the way, at least for custom elements, here's an easy way to implement What's nice is that this works regardless of parsing or code load order! During parsing, when a child (Any caveats I missed?) |
you just need MutationObserver for that |
That's part of the problem because it introduces unwanted Simple would mean it is called synchronously like |
custom elements are async by specifications, indeed driven by callbacks, so I'm not sure what's the issue that you are trying to solve. childConnected, in any case, doesn't solve the most important issue: when it is safe to setup the component. Until we have that, everything else looks superfluous to me 🤷♂️ |
Maybe "synchronous" isn't the right term, but from my experience, no macro tasks fire during parsing, and no animation frames either (at least during parsing of the body, though some seem to fire during parsing of the head). So once we're in parsing of the body (including custom elements), then it is almost like "synchronous" in the sense that there's no possible way to defer until after parsing except with See, here's an example: (Note I am hovering on Thus because it is impossible to use microtask-based deferral to wait for children to be ready, and because And as an ugly side effect of this, anything in a After doing the macrotask deferral in the custom element code, we then need to manually handle children once (because remember So, because of this, parsing is effectively "synchronous": Macrotasks don't fire until after parsing (from my observations). So, you see: This is a huge pain. In my custom elements, APIs and state are ready after both So if end users of the APIs need to reply on APIs and state that only exist after both Do you see the problem now? For example, suppose we have elements that create state in both <some-element>
<other-element>
</other-element>
</some-element>
<script>
// `childConnectedCallback` has not fired yet. And guess what: `connectedCallback` has
// to defer in order to manually fire `childConnectedCallback` in a future macrotask,
// because guess what: `MutationObserver` does NOT fire on initially connected children!
// So guess even more what! Because the elements must defer with a macrotask, then we
// (speaking as the end user of the custom elements) need to to defer with a macrotask:
const el = document.querySelector('some-element')
console.log(el.someStateCreatedInChildConnectedCallback) // undefined
Promise.resolve().then(() => {
console.log(el.someStateCreatedInChildConnectedCallback) // STILL undefined, WAT?
})
setTimeout(() => {
console.log(el.someStateCreatedInChildConnectedCallback) // FINALLY! Ugh.
})
</script> It's plain and simple: It'd be great to have a "synchronous" (in the sense I described above with regard to parsing) An easy way to implement it is with the monkey patching I linked to, or perhaps a class-factory mixin. |
Apparently, because children can not be observed during parsing with I don't know if this is part of spec though, but IMO definitely not ideal because it shows some behavior that isn't a normal part of what people expect from JavaScript. |
which is exactly my point since about ever: the most important piece of the puzzle to grant robust delivery of custom elements is not in the specifications and nobody knows, not even browser vendors, when an element can be trustfully initialized. Yes, we can always add nodes after that, but the point is that nobody knows when it's even possible to do so. We need to solve this part of the puzzle, or anything dealing with As summary, we need an |
As I've numerously stated in various threads & issues, the right approach is for the child to report its insertion to the parent. Waiting for all children to be parsed in order to initialize is never a good idiom because the HTML parser may pause for seconds at a time when there is a network delay. We used to have code like that in our builtin element implementations and we still do in some cases (due to historical reasons) but almost all such code end up causing bugs. It's simply not the right pattern by which to write an element. |
That's basically the idea in the example of #785 (comment).
Ah, that explains even more, so it's something stuck somewhere between microtasks and macrotasks.
It can be fine if the relationship between parent and child is part of the API. For example: In my cases, I have one example where a parent lays out children, but the children need to be a certain type: <!-- lays out nodes using Apple-style VFL -->
<i-autolayout-node
id="layout"
position="0 0 0"
align=" 0.5 0.5 0"
mount-point=" 0.5 0.5 0"
visual-format="
V:|-[child1(child3)]-[child3]-|
V:|-[child2(child4)]-[child4]-|
V:[child5(child4)]-|
|-[child1(child2)]-[child2]-|
|-[child3(child4,child5)]-[child4]-[child5]-|
"
style="background: rgba(0,0,0,0.3)"
>
<i-dom-plane size="1 1 0" color="deeppink" class="child1">This is a paragraph of text to show that it reflows when the size of the layout changes size so that the awesomeness can be observed in its fullness.</i-dom-plane>
<i-dom-plane size="1 1 0" color="deeppink" class="child2">This is a paragraph of text to show that it reflows when the size of the layout changes size so that the awesomeness can be observed in its fullness.</i-dom-plane>
<i-dom-plane size="1 1 0" color="deeppink" class="child3">This is a paragraph of text to show that it reflows when the size of the layout changes size so that the awesomeness can be observed in its fullness.</i-dom-plane>
<i-dom-plane size="1 1 0" color="deeppink" class="child4">This is a paragraph of text to show that it reflows when the size of the layout changes size so that the awesomeness can be observed in its fullness.</i-dom-plane>
<i-dom-plane size="1 1 0" color="deeppink" class="child5">This is a paragraph of text to show that it reflows when the size of the layout changes size so that the awesomeness can be observed in its fullness.</i-dom-plane>
</i-autolayout-node> |
Another issue is, that children can not report to their parents if they are in a shadow root. However, the parent can easily either look at its direct children, or look at children in its root. |
I'm thinking another approach can be to emit an event with |
@trusktr with events you could use the BTW, have you explored using the |
I made a pen based on the MDN example: https://codepen.io/trusktr/pen/66d328c9378e850f6419086d0624ead7 Anywho, turns out this is only good in Custom Elements, as we can at most enforce out own elements emit the events. Originally I mentioned:
@rniwa pointed out that if the I wanted to create an observer in I eventually figured the pattern to conditionally create the observer either in the Then I asked about being able to check |
nobody said that is the place indeed and you should add the observer within the constructor. The connected/disconnected can be either a flag or a dumb |
I just gave it a try, and it seems to return |
Yes, but So in the example I'm making the observer from both This is all under the assumption that a |
If a browser were to keep a |
Yeah, I don't use Shadow DOM much so I haven't thought about it. The @rniwa solution looks way better but I wonder if that should be a WHATWG proposal instead of just a comment in some discussion, 'cause it's very handy and easily badly replaced (see my suggestion, as example).
I think as @rniwa suggested it'd be unnecessary. Same goes for listeners, the constructor is the place to add these or remove these, while reaction can be different based on the node state. Adding and removing too much on each connect/disconnect is not better, IMO, just slower and, in this case, simply over complicated for no gain. |
Could be true sometimes, but in order to implement something like a childConnected/DisconnectedCallback feature which is inactive when the tree is not in a document (similar to connected/disconnectedCallback, it requires handling in connected/disconnected. Well, maybe we can just return early is not is connected, but then the MO would be firing all the time still. Would that be better? |
I think having such methods would make it much easier than using
MutationObserver
for doing things when children are connected to a custom element.Some reasons:
Suppose we wish to use
connectedCallback
to initiate a mutation observer, anddisconnectedCallback
to disconnect the observer, rather than initiating it increatedCallback
orconstructor
. This has the side-effect that by the timeconnectedCallback
is fired that the custom element may already have had children added to it, so the mutation observer won't catch those.Using MutationObserver requires writing at least two nested for loops, which is much more verbose than simply placing logic inside a
childConnectedCallback(child)
method. f.e. comparevs
The API is just easier, which is a good thing for people learning the Custom Element API.
On a similar note, it may be nice to have element-specific methods. For example (and as a possible alternative to my
distributedCallback
idea),HTMLSlotElement
could have achildDistributedCallback(child)
method, and it wouldbe possible to make a Custom Element that extends fromHTMLSlotElement
and to use it in place of the default<slot>
element.I know we'll be able to use
slotchange
events, but the methods might be easier to work with.The text was updated successfully, but these errors were encountered: