Concorde decorator — @bind. Binds a class property to a path in a publisher. The property updates when publisher data changes. Doc ID: docs/_decorators/bind. Keywords: Concorde, supersoniks, docs/_decorators/bind, bind, @bind, decorator, @state(), DataProviderKey, . Use , reflect: true, publisher.set(...). URL: https://concorde.supersoniks.org/crawl/docs/_decorators/bind.html.
@bind
Binds a class property to a path in a publisher. The property updates when publisher data changes.
For Lit re-renders, also add @state() on the same property.
See also: @subscribe, @handle, @publish, @get, @post, @put, @patch.
Principle
The decorator subscribes to the DataProvider store using dot notation or a DataProviderKey. Updates flow into the decorated property (Data flow).
Import
import { bind } from "@supersoniks/concorde/decorators";
Example
@customElement("demo-bind")
export class DemoBind extends LitElement {
static styles = [tailwind];
@bind("demoData.firstName")
@state()
firstName = "";
@bind("demoData.lastName")
@state()
lastName: string = "";
@bind("demoData.count")
@state()
count: number = 0;
render() {
return //......
}
updateData() {
set(demoDataKey, { ...get(demoDataKey), count: get(demoDataKey).count + 1 });
// see demo-bind in src/docs/example/decorators-demo-bind-demos.ts
const randomIndex = Math.floor(Math.random() * demoUsers.get().length);
const randomUser = demoUsers.get()[randomIndex];
demoData.set({
firstName: randomUser.firstName,
lastName: randomUser.lastName,
count: (demoData.count.get() || 0) + 1,
});
}
}
<docs-demo-sources for="demo-bind"></docs-demo-sources>
<demo-bind></demo-bind>
DataProviderKey (strict typing)
@bind accepts either a string path (legacy) or a DataProviderKey<T>. The property type must match T. Use reflect: true to push local writes back to the publisher (see below). See DataProviderKey.
import { bind } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
type Data = { count: number };
const dataKey = new DataProviderKey<Data>("data");
@bind(dataKey.count, { reflect: true })
@state()
count: number = 0;
Reflect (reflect: true)
Two-way sync: reads from the publisher and local assignments call publisher.set(...). An internal guard avoids infinite loops.
@bind("userData.profile.avatarUrl", { reflect: true })
@state()
avatar: string;
@customElement("demo-bind-reflect")
export class DemoBindReflect extends LitElement {
static styles = [tailwind];
@bind("bindReflectDemo.count", { reflect: true })
@state()
withReflect: number = 0;
@bind("bindReflectDemo.count")
@state()
withoutReflect: number = 0;
render() {
return html`
<div class="mb-3">
from publisher : ${sub("bindReflectDemo.count") || 0} <br />
from component with reflect : ${this.withReflect || 0} <br />
from component without reflect : ${this.withoutReflect || 0}
</div>
<sonic-button @click=${() => this.withReflect++}
>Increment with reflect</sonic-button
>
<sonic-button @click=${() => this.withoutReflect++}
>Increment without reflect</sonic-button
>
`;
}
}
<docs-demo-sources for="demo-bind-reflect"></docs-demo-sources>
<demo-bind-reflect></demo-bind-reflect>
Path syntax
- First segment: data provider id.
- Nested properties: dot notation.
- Arrays: numeric index (
items.0).
Dynamic paths
Use ${prop} or ${this.prop} inside a normal string literal (not a JS template literal with backticks). @bind re-subscribes when a reactive dependency changes. While a placeholder is null/undefined, the bind is inactive; optional { skipEmptyPlaceholder: true } also waits on "". See Dynamic path placeholders.
Properties referenced in the pattern must be reactive (
@property, etc.) or you must callrequestUpdatemanually.
<docs-demo-sources for="demo-bind-dynamic"></docs-demo-sources>
<demo-bind-dynamic></demo-bind-dynamic>
Behavior
- Subscribes at
connectedCallback, unsubscribes atdisconnectedCallback. - If the path does not exist yet, a publisher may be created with
null.
Notes
Works with any component that has the usual DOM lifecycle (LitElement, Subscriber mixin, etc.).
Shared data: Sharing data.