lwc
lwc copied to clipboard
Private fields and methods with LightningElement
Question originally posted to StackExchange.
Now that the private field and method proposal reached stage 4, I think it's a good time to discuss how those play out with LWC components.
Back story
Component encapsulation is a key part of LWC since its inception. Until the release of private fields and methods, there was no easy way to hide internal implementation details with web components. LWC does some magic under the hood to emulate this.
By default LWC components fields and methods are private. The @api decorator can be used on a class field or method to indicate that it should be made public. The trick is that the LWC component instance is a different object than the custom element (in native web components it's a single object). The LWC framework at runtime does forward the component public fields and methods to the associated host element.
// Native web compoennt
class extends HTMLElement {
connectedCallback() {
console.log(this instanceof Element) // true
}
}
// LWC component
class extends LightningElement {
connectedCallback() {
console.log(this instanceof Element) // false
}
}
Observations and open questions
- Replacing
@apiwith the usage of private fields and methods is a breaking change. It would require some enablement mechanism for components to opt-in. - It is not only a breaking change from an API perspective but it also breaks the existing developers' metal model concerning LWC component encapsulation. Until now all fields and methods were considered private, this model is flipped if fields and methods are public.
- Adopting private fields as a mechanism to hide component internals is a step in the long-term vision we have to merge the component instance with the custom element class.
- Private fields are scoped to the enclosing class.
- How to access private fields from the template if the component template lives outside the component class?
- What is the story around component inheritance? Today encapsulation mechanism permit the child class to access parent fields without them behind exposed to the outside world.
How to access private fields from the template if the component template lives outside the component class?
I take it this is due to the fact that the compiler treats each file individually, correct? I.e. it cannot take code from the template HTML file and "inject" it into the associated JavaScript file.
I take it this is due to the fact that the compiler treats each file individually, correct?
Yes, this is true. That said, not only the template has to be injected into the JavaScript file, it has to be injected into the associated JavaScript class. This opens new questions with component inheritance, complex JavaScript files, and existing capabilities to import multiple templates for the same component.
Maybe it's worth revising this, then. I was thinking recently about how we might allow arbitrary JavaScript expressions in templates (e.g. <template if:true={foo % 2 === 0}>), and it seems the most straightforward way would be to automatically generate getters from the template. If we did this, then they should have access to private variables.
Maybe it's worth revising this, then. I was thinking recently about how we might allow arbitrary JavaScript expressions in templates (e.g.
<template if:true={foo % 2 === 0}>), and it seems the most straightforward way would be to automatically generate getters from the template. If we did this, then they should have access to private variables.
As in, invisible getters that are only available in the template, but not by other components? Is that something that can be enforced?
I'm not sure it would be feasible use private class methods (the engine would probably still need to access the method from the outside), but we could at least obfuscate the name or use Symbols or something to make it impractical for anyone but the engine to use.
but we could at least obfuscate the name or use Symbols or something to make it impractical for anyone but the engine to use.
I don't think it would make the cut from a security standpoint. Name obfuscation or the usage of symbols doesn't make the property private as it is still observable from the outside using Object.getOwnProperties or Object. getOwnPropertySymbols.