A Quick Note on Lit Types & Properties
This is going to be a brief one, just to keep a note somewhere of how to do these things and what they mean.
Lately, I’ve seen some confusion from the Polymer community and consumers of the new LitElement…
The confusion? What are property types? How do we use properties? How do things serialize and deserialize? How does this compare to Polymer?
This post only applies if you pass values by attributes or reflect your properties!
Well here we are…
Polymer
I’m going to assume if you’re reading this, you’re aware of how Polymer works and how to use the basics of it.
We define a property like so:
class MyElement extends PolymerElement {
static get properties() {
return {
prop1: String,
prop2: Boolean,
prop3: Object,
prop4: Array,
prop5: { type: Array, notify: true }
};
}
}
Polymer does some wizardry around this so pretty much all primitives and built in types are parsed correctly.
<my-element prop4="[1,2,3]"></my-element>
The above will result in prop4
being the array, [1, 2, 3]
after
Polymer deserializes it.
Even Date
is parsed correctly and Object
is parsed as JSON.
LitElement
Pretty much everyone who came from Polymer makes the mistake of trying the same property types in Lit as they used in Polymer.
In Lit though, I suppose the idea is to remain lightweight, trimmed down and lacking of any fancy logic like what Polymer had. This means most of these types are not supported out of the box.
We define a property the same (which is why this assumption is an easy one to make):
class MyElement extends LitElement {
static get properties() {
return {
prop1: { type: String },
prop2: { type: Boolean },
prop3: { type: Object },
prop4: { type: Array }
};
}
}
This won’t work!
Let’s take a look:
<my-element prop1="foo">
gives usnode.prop1 === "foo"
<my-element prop2="true">
gives usnode.prop2 === true
<my-element prop2="false">
gives usnode.prop2 === true
<my-element prop3='{ "foo": 5 }'>
gives usnode.prop3 === '{ "foo" : 5 }'
<my-element prop4="[1,2,3]">
gives usnode.prop4 === "[1,2,3]"
You can see the last 3 are confusingly wrong. This is likely not what you expected at all.
How it works
A property type in Lit is a serializer object or function.
When we node.setAttribute('prop1', 'foo')
or <my-el prop1="foo">
, we
actually end up invoking node.prop1 = String('foo')
.
Whatever we set our type
to is used to deserialize the attribute value.
With type: Foo
, we would result in node.prop1 = Foo('foo')
.
It can be one of two possible types…
A Function
const deserializer = (str) => JSON.parse(str);
In this case, given the element above but with:
{
prop3: { type: deserializer, reflect: true },
prop4: { type: deserializer, reflect: true }
}
We would end up with:
<my-element prop3='{ "foo": 5 }'></my-element>
<!--
node.prop3 would be the object { "foo" : 5 }
-->
<my-element prop4="[1,2,3]"></my-element>
<!--
node.prop4 would be the array [1, 2, 3]
-->
<!--
Setting node.prop4 = [4, 5, 6] will reflect:
-->
<my-element prop4="4,5,6"></my-element>
A function works when we only want special parsing but are happy with
toString
when reflecting to the attribute.
You can see from the last example, we end up serializing into 4,5,6
because
that is what [4,5,6].toString()
results in.
An Object
For cases where we want to handle serialization for reflecting values to attributes, such as in the case of arrays, we need to provide an object:
const deserializer = {
toAttribute(val) {
return JSON.stringify(val);
}
fromAttribute(str) {
return JSON.parse(str);
}
};
Now we see:
<my-element prop4="[1,2,3]"></my-element>
<!--
node.prop4 would be the array [1, 2, 3]
-->
<!--
Setting node.prop4 = [4, 5, 6] will reflect:
-->
<my-element prop4="[4,5,6]"></my-element>
The value is serialized correctly!
Built-ins
Boolean
, Number
and String
do work out of the box, kind of.
Now that you know how it works:
Boolean('true') === true
Boolean('false') === true
Number('5') === 5
String('foo') === 'foo'
You see, these constructors happen to behave the same way we want a
deserializer function to behave (though Boolean
is slightly different).
Boolean is different because Lit handles null
as false
, for you.
Conclusion
It is a bit confusing, yes.
It definitely means your Polymer properties are not compatible
with Lit, as you likely use Object
and such a lot.
Lit may end up implementing these common serializers, though, seeing as pretty much everyone would expect to use them. We shall see, as it would also introduce more weight into Lit and that is what the team wants to avoid.
Until then, keep an eye on a little repo I’m playing around with as it aims to provide these common serializers until there’s a better solution (or forever if there never is one).
Enjoy!