-
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
[element-internals] How to get internals in base class and subclass, without leaking it to public #962
Comments
Maybe, as long as we're still in construction, |
We need an JavaScript equivalent of "protected" keyword in C++/Java to do this. |
The work-arounds for this, for now, are to either override class Base extends HTMLElement {
#internals
#internalsCalled = false
constructor() {
super()
this.attachInternals()
this.#internalsCalled = false
}
attachInternals() {
if (this.#internals && !this.#internalsCalled) {
this.#internalsCalled = true
return this.#internals
}
this.#internals = super.attachInternals()
this.#internalsCalled = true
return this.#internals
}
} Or to provide some agreed upon interface in your own libraries, like |
@keithamus But in your example, if a subclass of the base class doesn't need the internals (not all end subclasses may need them) then your example leaves the extra call available for the public. The following is still a problem, isn't it? customElements.define('my-el', class extends HTMLElement {})
document.createElement('my-el').attachInternals() // returns "internals" to the public. (I had expected an error) I originally thought that And, if So, if we can't actually guarantee that Maybe it should be called |
I'm interested in plain JS semantics because as a library author, I can't guarantee my users will use TypeScript. |
Right. My example just works around the error condition of calling attachInternals twice while preserving it for later callsites. It doesn't attempt to fix the issue of outside access. If you know you don't want others to have access to internals and you don't want to call attachInternals yourself, you can disable it using customElements.define('my-el', class extends HTMLElement {
static get disabledFeatures() { return ['internals']; }
})
document.createElement('my-el').attachInternals() // throws |
You can always save it in a WeakMap and provide some API behind a closure to keep it hidden. It’s not particularly performant but it’s probably the best bet. Regardless, this is a perfect example of the need for |
Is there something wrong with the obvious approach of explicit communication? class Base extends HTMLElement {
#_
constructor(forwardInternals) {
super()
let internals = this.attachInternals()
this.#_ = internals
forwardInternals?.(internals)
}
}
class MyEl extends Base {
#_
constructor() {
// if you want to be open for further extension, repeat the `forwardInternals` thing in this constructor
let internals;
super(int => { internals = int })
this.#_ = internals
}
} |
That API seems backwards. Perhaps it should have been this: customElements.define('my-el', class extends HTMLElement {})
document.createElement('my-el').attachInternals() // throws customElements.define('my-el', class extends HTMLElement {
static enabledFeatures = ['internals']
})
document.createElement('my-el').attachInternals() // does not throw Always avoid double negatives! But that's tangential anyway. Once the feature is available, protecting it is difficult. |
Related discussion in TC39 ES forum: |
@bakkot interesting trick. That does indeed keep the internals protected. The only downside is subclasses needing to remember that boilerplate. That subclass constructor should also wire up a callback in case a further subclass needs internals (lots of boilerplate). And then it gets tricky with class-factory mixins. |
With bakkot’s trick, that does cause complications calling |
Well, if you forget it, you won't get access to the internals, so I don't think there's much risk of that. And if you don't need the internals you don't need to include the boilerplate in your subclass.
What complications do you mean? |
But if you forget it, then a further subclass of your class can't get them either, so you need the boilerplate even if your class doesn't need internals. |
Personally I am inclined to regard it as a good thing that you need to explicitly opt in to be open for extensions which can manipulate internal state, rather than that being the default, but this is a design question which is beyond the scope of this thread. |
F2F resolution: Closing this bug. Each custom element class needs to be designed with subclassing in mind. This is working as intended. |
comment moved to |
How does a sub class get the internals? This isn't working:
The error in Chrome is:
The Tc39 ES group thought
protected
was not a worthy thing to give us. What do we do, how do we keep it within the class hierarchy without leaking to public? Implement a complicated dance?The text was updated successfully, but these errors were encountered: