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: JSON
An 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: JSON
An 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 LitElement
Base 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) => CustomElementDecorator
Class decorator factory that defines the decorated class as a custom element.
, propertyfunction property(options?: PropertyDeclaration): PropertyDecorator
A 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): CustomElementDecorator
Class decorator factory that defines the decorated class as a custom element.
('my-lit-element')
export class MyLitElementclass MyLitElement
extends LitElementclass LitElement
Base 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): PropertyDecorator
A 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?: unknown
Indicates 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: ObjectConstructor
Provides 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): ShadowRoot
Creates 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: JSON
An 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): any
Converts a JavaScript Object Notation (JSON) string into an object.
(attrDataconst attrData: string
);
this.shadowRootElement.shadowRoot: ShadowRoot | null
Returns 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: CustomElementRegistry
Defines 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.RouteModule
Defines 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) => RouteModule
Defines a file-based route for Gracile to consume.
({
documentdocument?: DocumentTemplate<{
url: URL;
props: undefined;
params: Parameters;
}> | undefined
A 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;
}> | undefined
A 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: Math
An intrinsic object that provides basic mathematics functionality and constants.
.randomMath.random(): number
Returns 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 LitElement
Base 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) => CustomElementDecorator
Class decorator factory that defines the decorated class as a custom element.
, propertyfunction property(options?: PropertyDeclaration): PropertyDecorator
A 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): CustomElementDecorator
Class decorator factory that defines the decorated class as a custom element.
('serialization-example')
export class SerializationExampleclass SerializationExample
extends LitElementclass LitElement
Base 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): PropertyDecorator
A 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> | undefined
Indicates 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): unknown
Called 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(): number
Gets 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): boolean
Returns 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
).