Passing data from server to client
When you output Web Components from the server, with or without the declarative shadow DOM pre-render, it’s often desirable to initialize them with server-generated, complex data.
Instead of reinventing the wheel, Gracile is just providing you the guidelines on how to achieve that, well… gracefully 😌.
Note that this isn’t even a JS framework thing. The patterns described below are transposable for any HTML-outputting backends.
Typically you’ll find two philosophies:
- Put the hydrating data in a
<script type="application/json">{...}</script>, and from there the client-side framework will pick that up to hydrate the component tree by reconciling it with data.
Examples: Nuxt, Next, Gatsby… - Put the hydrating data in a child element attribute, typically called an “Island”.
Examples: Astro, Fresh,…
With Lit SSR you can use both methods, though. The first one is described here.
In this mini-guide, we’ll focus on the “Island-ey” pattern, for now.
Custom Element examples
📄 /src/features/my-partial.ts
import { htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
Will handle server rendering just fine as well!
import './my-lit-element.js';
import type { InitialDatatype InitialData = any } from './my-lit-element.js';
export type InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
} = { foofoo?: string | undefined ?: string; bazbaz?: number | null | undefined ?: null | number };
const dataconst data: InitialData = { foofoo: string : 'bar', bazbaz: number : 2 } satisfies InitialDatatype InitialData = any ;
export const myServerRenderedPartialconst myServerRenderedPartial: TemplateResult<1> = htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<!-- ... -->
<section>
<my-lit-element initialData=${JSONvar JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.stringifyJSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
(dataconst data: InitialData )}></my-lit-element>
</section>
<section>
<my-bare-element data-initial=${JSONvar JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.stringifyJSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
(dataconst data: InitialData )}></my-bare-element>
</section>
<!-- ... -->
`;
📄 /src/features/my-partial.client.ts
import './my-lit-element.ts';
import './my-bare-element.ts';
📄 /src/features/my-lit-element.ts
import { LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
, htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { customElementconst customElement: (tagName: string) => CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
, propertyfunction property(options?: PropertyDeclaration): PropertyDecoratorA class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked property when clicked and fires a changed event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
} from 'lit/decorators.js';
import type { InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
} } from './my-partial.ts';
@customElementfunction customElement(tagName: string): CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
('my-lit-element')
export class MyLitElementclass MyLitElement extends LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
{
@propertyfunction property(options?: PropertyDeclaration<unknown, unknown> | undefined): PropertyDecoratorA class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked property when clicked and fires a changed event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
({ typePropertyDeclaration<unknown, unknown>.type?: unknownIndicates the type of the property. This is used only as a hint for the
converter to determine how to convert the attribute
to/from a property.
: Objectvar Object: ObjectConstructorProvides functionality common to all JavaScript objects.
})
initialDataMyLitElement.initialData: string | InitialData : InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
} | string = {};
renderMyLitElement.render(): TemplateResult<1>Invoked on each update to perform rendering tasks. This method may return
any value renderable by lit-html's ChildPart - typically a
TemplateResult. Setting properties inside this method will not trigger
the element to update.
() {
if (typeof this.initialDataMyLitElement.initialData: string | InitialData !== 'object') return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`Invalid data`;
return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<!-- -->
<div>${this.initialDataMyLitElement.initialData: InitialData .foofoo?: string | undefined }</div>
<div>${this.initialDataMyLitElement.initialData: InitialData .bazbaz?: number | null | undefined || 'Oh no'}</div>
`;
}
}
📄 /src/features/my-bare-element.js
import type { InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
} } from './my-partial.ts';
class MyBareElementclass MyBareElement extends HTMLElementvar HTMLElement: {
new (): HTMLElement;
prototype: HTMLElement;
}Any HTML element. Some elements directly implement this interface, while others implement it via an interface that inherits it.
{
#initialData?: InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
} ;
connectedCallbackMyBareElement.connectedCallback(): void () {
this.attachShadowElement.attachShadow(init: ShadowRootInit): ShadowRootCreates a shadow root for element and returns it.
({ modeShadowRootInit.mode: ShadowRootMode : 'open' });
const attrDataconst attrData: string | undefined = this.datasetHTMLOrSVGElement.dataset: DOMStringMap .initialDOMStringMap[string]: string | undefined ;
if (attrDataconst attrData: string | undefined ) this.#initialData = JSONvar JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.parseJSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): anyConverts a JavaScript Object Notation (JSON) string into an object.
(attrDataconst attrData: string );
this.shadowRootElement.shadowRoot: ShadowRoot | nullReturns element's shadow root, if any, and if shadow root's mode is "open", and null otherwise.
!.innerHTMLInnerHTML.innerHTML: string = `
<div>${this.#initialData?.foofoo?: string | undefined || 'Not defined!'}</div>
<div>${this.#initialData?.bazbaz?: number | null | undefined || 'Not defined!'}</div>
`;
}
}
customElementsvar customElements: CustomElementRegistryDefines a new custom element, mapping the given name to the given constructor as an autonomous custom element.
.defineCustomElementRegistry.define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void ('my-bare-element', MyBareElementclass MyBareElement );
Another big win by leveraging Web Component intrinsic versatility!
No need to bring superfluous JS kilobytes to do this hard work, it’s already
embedded in your browser.
Please note that these examples could be improved, notably for:
initialData: InitialData | string = {};
string is just for the ts-lit-plugin to be appeased inside the html template.
Also, initialData and initialdata are equivalent, but hyphenated properties are rejected by the linter.
Maybe a bit more elegant solutions could be achieved, but those a more convention/typing issues, not runtime ones.
Advanced serialization / deserialization
If you want to pass more complex data like object with self referencing children, dates, and more, you can use a dedicated library for that, there are plenty.
For example the excellent Seroval library can be used in conjunction with a Lit @property custom converter to properly “revive” the serialized data.
This is a fully working example:
📄 /src/routes/serialization.ts
import { defineRoutefunction defineRoute<GetHandlerData extends R.HandlerDataHtml = undefined, PostHandlerData extends R.HandlerDataHtml = undefined, CatchAllHandlerData extends R.HandlerDataHtml = undefined, StaticPathOptions extends R.StaticPathOptionsGeneric | undefined = undefined, RouteContext extends R.RouteContextGeneric = {
...;
}>(options: {
handler?: StaticPathOptions extends object ? never : R.Handler<CatchAllHandlerData> | {
GET?: R.Handler<GetHandlerData>;
POST?: R.Handler<PostHandlerData>;
QUERY?: R.Handler<Response>;
PUT?: R.Handler<Response>;
PATCH?: R.Handler<Response>;
DELETE?: R.Handler<Response>;
HEAD?: R.Handler<Response>;
OPTIONS?: R.Handler<Response>;
} | undefined;
staticPaths?: () => R.MaybePromise<StaticPathOptions[]> | undefined;
prerender?: boolean | undefined;
document?: R.DocumentTemplate<RouteContext> | undefined;
template?: R.BodyTemplate<RouteContext> | undefined;
}): (RouteModule: typeof R.RouteModule) => R.RouteModuleDefines a file-based route for Gracile to consume.
} from '@gracile/gracile/route';
import { htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { serializefunction serialize<T>(source: T, options?: SyncParserContextOptions): string } from 'seroval';
import { documentconst document: (props: {
url: URL;
}) => ServerRenderedTemplate } from '../document.js';
import { complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} } from './_serialization-demo-data.js';
export default defineRoutedefineRoute<undefined, undefined, undefined, undefined, {
url: URL;
props: undefined;
params: Parameters;
}>(options: {
handler?: Handler<undefined> | {
GET?: Handler<undefined> | undefined;
... 6 more ...;
OPTIONS?: Handler<...> | undefined;
} | undefined;
staticPaths?: (() => MaybePromise<...> | undefined) | undefined;
prerender?: boolean | undefined;
document?: DocumentTemplate<...> | undefined;
template?: BodyTemplate<...> | undefined;
}): (RouteModule: typeof RouteModule) => RouteModuleDefines a file-based route for Gracile to consume.
({
documentdocument?: DocumentTemplate<{
url: URL;
props: undefined;
params: Parameters;
}> | undefinedA function that returns a server only template.
Route context is provided at runtime during the build.
: (contextcontext: {
url: URL;
props: undefined;
params: Parameters;
} ) => documentfunction document(props: {
url: URL;
}): ServerRenderedTemplate (contextcontext: {
url: URL;
props: undefined;
params: Parameters;
} ),
templatetemplate?: BodyTemplate<{
url: URL;
props: undefined;
params: Parameters;
}> | undefinedA function that returns a server only or a Lit client hydratable template.
Route context is provided at runtime during the build.
: () => htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<serialization-example
myComplexData=${serializeserialize<{
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
}>(source: {
...;
}, options?: SyncParserContextOptions | undefined): string (complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} )}
></serialization-example>
`,
});
📄 /src/routes/_serialization-demo-data.ts
export const complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} = {
numbernumber: readonly [number, 0, number, number, number] : [Mathvar Math: MathAn intrinsic object that provides basic mathematics functionality and constants.
.randomMath.random(): numberReturns a pseudorandom number between 0 and 1.
(), -0, NaNvar NaN: number , Infinityvar Infinity: number , -Infinityvar Infinity: number ],
stringstring: readonly ["hello world", "<script>Hello World</script>"] : ['hello world', '<script>Hello World</script>'],
booleanboolean: readonly [true, false] : [true, false],
nullnull: null : null,
undefinedundefined: undefined : undefinedvar undefined ,
bigintbigint: 9007199254740991n : 9007199254740991n,
arrayarray: readonly [undefined, undefined, undefined] : [, , ,], // holes
regexpregexp: RegExp : /[a-z0-9]+/i,
datedate: Date : new Datevar Date: DateConstructor
new () => Date (+4 overloads) (),
mapmap: Map<string, string> : new Mapvar Map: MapConstructor
new <string, string>(iterable?: Iterable<readonly [string, string]> | null | undefined) => Map<string, string> (+3 overloads) ([['hello', 'world']]),
setset: Set<string> : new Setvar Set: SetConstructor
new <string>(iterable?: Iterable<string> | null | undefined) => Set<string> (+1 overload) (['hello', 'world']),
} as consttype const = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} ;
export type MyDatatype MyData = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} = typeof complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} ;
// self cyclic references
// recursive objects
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .self = complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} ;
// recursive arrays
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .arrayarray: readonly [undefined, undefined, undefined] .push(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .arrayarray: readonly [undefined, undefined, undefined] );
// recursive maps
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .mapmap: Map<string, string> .setMap<string, string>.set(key: string, value: string): Map<string, string>Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
('self', complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .mapmap: Map<string, string> );
// recursive sets
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .setset: Set<string> .addSet<string>.add(value: string): Set<string>Appends a new element with a specified value to the end of the Set.
(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .setset: Set<string> );
// mutual cyclic references
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .arrayarray: readonly [undefined, undefined, undefined] .push(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .mapmap: Map<string, string> );
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .mapmap: Map<string, string> .setMap<string, string>.set(key: string, value: string): Map<string, string>Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
('mutual', complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .setset: Set<string> );
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .setset: Set<string> .addSet<string>.add(value: string): Set<string>Appends a new element with a specified value to the end of the Set.
(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .arrayarray: readonly [undefined, undefined, undefined] );
📄 /src/routes/serialization.client.ts
import { LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
, htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { customElementconst customElement: (tagName: string) => CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
, propertyfunction property(options?: PropertyDeclaration): PropertyDecoratorA class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked property when clicked and fires a changed event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
} from 'lit/decorators.js';
import { serializefunction serialize<T>(source: T, options?: SyncParserContextOptions): string , deserializefunction deserialize<T>(source: string): T } from 'seroval';
import type { MyDatatype MyData = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} } from './_serialization-demo-data.js';
@customElementfunction customElement(tagName: string): CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
('serialization-example')
export class SerializationExampleclass SerializationExample extends LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
{
@propertyfunction property(options?: PropertyDeclaration<unknown, unknown> | undefined): PropertyDecoratorA class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked property when clicked and fires a changed event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
({
converterPropertyDeclaration<unknown, unknown>.converter?: AttributeConverter<unknown, unknown> | undefinedIndicates how to convert the attribute to/from a property. If this value
is a function, it is used to convert the attribute value a the property
value. If it's an object, it can have keys for fromAttribute and
toAttribute. If no toAttribute function is provided and
reflect is set to true, the property value is set directly to the
attribute. A default converter is used if none is provided; it supports
Boolean, String, Number, Object, and Array. Note,
when a property changes and the converter is used to update the attribute,
the property is never updated again as a result of the attribute changing,
and vice versa.
: {
fromAttributeComplexAttributeConverter<unknown, unknown>.fromAttribute?(value: string | null, type?: unknown): unknownCalled to convert an attribute value to a property
value.
: (valuevalue: string | null ) =>
valuevalue: string | null ? deserializedeserialize<{
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
}>(source: string): {
...;
} <MyDatatype MyData = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} >(valuevalue: string ) : undefinedvar undefined ,
},
})
Reminder that `string` is used to prevent a type error in the template.
myComplexDataSerializationExample.myComplexData?: string | {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} | undefined ?: MyDatatype MyData = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} | string;
renderSerializationExample.render(): TemplateResult<1>Invoked on each update to perform rendering tasks. This method may return
any value renderable by lit-html's ChildPart - typically a
TemplateResult. Setting properties inside this method will not trigger
the element to update.
() {
if (typeof this.myComplexDataSerializationExample.myComplexData?: string | {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} | undefined !== 'object') return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`Invalid data`;
return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<span>Dump</span>
<pre>${serializeserialize<{
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
}>(source: {
...;
}, options?: SyncParserContextOptions | undefined): string (this.myComplexDataSerializationExample.myComplexData?: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} )}</pre>
<!-- -->
${this.myComplexDataSerializationExample.myComplexData?: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .mapmap: Map<string, string> .sizeMap<string, string>.size: number }
<!-- -->
${this.myComplexDataSerializationExample.myComplexData?: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .datedate: Date .getFullYearDate.getFullYear(): numberGets the year, using local time.
()}
<!-- -->
${this.myComplexDataSerializationExample.myComplexData?: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
... 6 more ...;
readonly set: Set<...>;
} .regexpregexp: RegExp .testRegExp.test(string: string): booleanReturns a Boolean value that indicates whether or not a pattern exists in a searched string.
('Regexp test string')}
`;
}
}
Refreshing data without a full page reload
For that, just set up a JSON handler in your route.
Then, connect this pipe to your Web Components properties and interactions.
Could be bidirectional, too (GET/POST → Response).