Serialization & Deserialization
Custom elements interact with the DOM either via HTML attributes (always strings) or JavaScript properties. Stencil automatically tries to keep properties and attributes in-sync when possible via serialization (turning properties into strings) and deserialization (turning strings back into properties).
For example, if you have a component defined like this:
@Component({
tag:'my-component',
})
exportclassMyComponent{
// Stencil 'sees' this as a number type.
// Numbers are easy to convert to/from strings
@Prop({ reflect:true}) myNumber:number;
}
When the property is set via JavaScript:
const myComponent = document.querySelector('my-component');
myComponent.myNumber =42;
Stencil will automatically serialize myNumber to an attribute:
<my-componentmy-number="42"></my-component>
Conversely, if the attribute is set in HTML:
<!-- in html -->
<my-componentmy-number="42"></my-component>
<!-- or js -->
<script>
const myComponent = document.querySelector('my-component');
myComponent.setAttribute('my-number', '43');
</script>
Stencil will automatically deserialize the attribute back to the property:
console.log(myComponent.myNumber);// 43
Most of the time Stencil's automatic serialization and deserialization is enough - especially with primitive data types, however there are cases where you might want to customize this behavior, especially when dealing with complex data.
The PropSerialize Decorator (@PropSerialize())
The @PropSerialize() decorator allows you to define custom serialization logic; converting a JavaScript property to a attribute string. The decorator accepts a single argument; the name of the class member @Prop() it is associated with. A method decorated with @PropSerialize() will automatically run when its associated property changes.
import{Component,Prop,PropSerialize}from'@stencil/core';
@Component({
tag:'my-component',
})
exportclassMyComponent{
@Prop() aStringArray:string[];
@PropSerialize('aStringArray')
serializeStringArray(value:string[]){
try{
returnJSON.stringify(value);// must return a string
}catch(e){
returnnull;// returning null removes the attribute
}
}
}
In the example above, the serializeStringArray method will run whenever the aStringArray property changes - the returned value will be used to update the attribute (no need to set {reflect: true} on the @Prop() decorator). E.g.
const myComponent = document.querySelector('my-component');
myComponent.aStringArray =['Hello','World'];
Becomes:
<my-componenta-string-array='["Hello","World"]'></my-component>
The AttrDeserialize Decorator (@AttrDeserialize())
The @AttrDeserialize() decorator allows you to define custom deserialization logic; converting an attribute string to a JavaScript property. The decorator accepts a single argument; the name of the class member @Prop() it is associated with. A method decorated with @AttrDeserialize() will automatically run when its associated attribute changes.
import{Component,Prop,AttrDeserialize}from'@stencil/core';
@Component({
tag:'my-component',
})
exportclassMyComponent{
@Prop() aStringArray:string[];
@AttrDeserialize('aStringArray')
deserializeStringArray(value:string):string[]|null{
try{
returnJSON.parse(value);
}catch(e){
returnnull;
}
}
}
In the example above, the deserializeStringArray method will run whenever the a-string-array attribute changes. The method takes the new value of the attribute as an argument and must return the deserialized value.
Now, when you set the attribute in HTML:
<my-componenta-string-array='["Hello","World"]'></my-component>
Stencil will automatically deserialize the attribute back to the property:
const myComponent = document.querySelector('my-component');
console.log(myComponent.aStringArray);// ['Hello', 'World']
Practical uses of PropSerialize
Practically speaking, there is little disadvantage in using a @AttrDeserialize() on a complex property; it just adds another method for users to provide data to your component.
The use-cases around using @PropSerialize() is slightly less obvious as in general, it is not considered best practice to reflect complex data (like objects or arrays) as attributes
The following example illustrates a practical use case for @PropSerialize() using the hydrate script output on a server we can fetch and serialize complex data to an attribute. When the same component loads in a browser, the component can de-serialize the data immediately without having to do another fetch.
import{AttrDeserialize,Build,Component, h,Prop,PropSerialize}from'@stencil/core';
interfaceUser{
userName:string;
avatarUrl:string;
posts:any[]
}
@Component({
tag:'user-login-panel',
})
exportclassUserLogin{
@Prop() user:User;
// On the server *only* let's represent the user's data as an attribute
// this allows the browser to get the data immediately without having to do a client-side fetch
@PropSerialize('user')
userSerialize(newVal:User){
if(Build.isBrowser){
returnnull;
}
try{returnJSON.stringify(newVal);}
catch(e){returnnull;}
}
// Whenever we have an attribute (including on client init)
// let's turn it back into an object that we can use and render
@AttrDeserialize('user')
userDeserialize(newVal:string){
try{returnJSON.parse(newVal);}
catch(e){returnnull;}
}
asynccomponentWillLoad(){
// On the server *only*, let's do a secret login involving private keys etc.
if(Build.isServer){
// Because we have a serializer method,
// setting a value automatically reflects it to the dom attribute
this.user=login(credentials);
}
}
render(){
if(this.user)return(`Welcome ${this.user.userName}!`);
elsereturn(`Please login`);
}
}