Skip to main content
Version: Next

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',
})
export class MyComponent {
// 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-component my-number="42"></my-component>

Conversely, if the attribute is set in HTML:

<!-- in html -->
<my-component my-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',
})
export class MyComponent {
@Prop() aStringArray: string[];

@PropSerialize('aStringArray')
serializeStringArray(value: string[]) {
try {
return JSON.stringify(value); // must return a string
} catch (e) {
return null; // 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-component a-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',
})
export class MyComponent {
@Prop() aStringArray: string[];

@AttrDeserialize('aStringArray')
deserializeStringArray(value: string): string[] | null {
try {
return JSON.parse(value);
} catch (e) {
return null;
}
}
}

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-component a-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';

interface User {
userName: string;
avatarUrl: string;
posts: any[]
}

@Component({
tag: 'user-login-panel',
})
export class UserLogin {
@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) {
return null;
}
try { return JSON.stringify(newVal); }
catch (e) { return null; }
}

// 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 { return JSON.parse(newVal); }
catch (e) { return null; }
}

async componentWillLoad() {

// 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}!`);
else return (`Please login`);
}
}