True Compile/Run-time privacy in WebComponents with TypeScript 2.7
Hey everyone! As you’ve may noticed, TypeScript 2.7 was released not long time ago and it brings a lot of new features to the table.
In this article we will focus primarily on Unique Symbol Types support, in particular within ES2015 classes, which are also a primary building block for WebComponents.
For our demonstration purpose, we will implement a simple WebComponent for showing/hiding projected content, with following features:
- custom element name:
my-toggleable
- it is both stateful and controllable component
- public API contains 2 props:
title
andshow
and 1 default content projection viaslot
. title
is gonna render within shadow-domshow
will toggle the content projection visibility from inside the component, or from parent, by setting values to this property ( controlled component pattern )- on host click, it will toggle visibility of projected content
Usage:
<my-toggleable>
<p>Some text Lorem ipsum dolor sit amet.</p>
</my-toggleable>
Demo:
Ok lets implement our Toggleable
WebComponent:
As we know, standard pattern to implement Custom Element props is following:
- internal “private” class property which keeps current property value
- getter to that “private” property for public use
- setter for “private” property for public use
So let’s say for our title
prop, following needs to be implemented
class Toggleable extends HTMLElement {
private _title = 'No title provided'
set title(value: string) {
this._title = value
this.render()
}
get title() {
return this._title
}
}
Because we are using TypeScript, we can annotate our internal property with private
accessor.
Whole Custom Element implementation, with various private
accessors
While this will indeed mitigate the direct access during compile time, it will be still accessible/present in the runtime, and because of that, it's not truly private!
Whoops!
Privacy during compile time
Privacy is gone during run time
Now what ???
Well there are private properties coming soon to EcmaScript classes but we are not there yet…
Until then we can use Symbol
primitive type in combination with class/object dynamic property definition
. This technique wasn't possible before with TypeScript, thankfully TS 2.7 adds this feature.
First let’s define our private symbols for all private properties of our class:
const titleProp = Symbol('title')
const showProp = Symbol('show')
const viewProp = Symbol('view')
const toggle = Symbol('toggle')
Now our title
CustomElement API definition will look following:
class Toggleable extends HTMLElement {
[titleProp] = 'No title provided'
set title(value: string) {
this[titleProp] = value
this.render()
}
get title() {
return this[titleProp]
}
}
Whole Custom Element implementation, with true compile and runtime privacy for particular class properties
Privacy during compile time
Privacy during run time
Note:
As we saw in the gif, you cannot create identical symbol, by yourself by naively creating Symbol with same string as in our implementation, and access the class dynamic property, because Symbols are always unique!
Only possible way how to access our private properties is to export those symbols and use them as a reference.
But in real life, there isn’t a valid use case to do that, so please don’t ;)
Nice, isn’t it ?
Stay type-safe and truly private within your WebComponent or Class implementations thanks to latest additions to TypeScript 2.7
As always, don’t hesitate to ping me if you have any questions here or on twitter (my handle @martin_hotell) and besides that, happy type checking folks and ‘till next time! Cheers!