Documentation

Bind

Surgical, declarative data binding from signals straight into the DOM.

Bind is the surgical alternative to re-rendering a subtree: project a domain StateSignal into a flat "view model" of presentation primitives, then bind DOM text, values, and attributes to it directly via HTML attributes. No Virtual DOM diffing — each binding updates exactly the node it targets.

View models

Register a namespace once, mapping your domain signal's value into whatever flat shape the view actually needs:

ts
import {setupBindDirectives, service_binding, createStateSignal} from '@alwatr/flux';

// Registers bind_text / bind_value / bind_attrib / bind_css_var — call once at bootstrap.
// The boolean controls whether bootstrapDirectives() runs automatically afterward.
setupBindDirectives(false);

const userSignal = createStateSignal({
  name: 'user',
  initialValue: {firstName: 'Ali', lastName: 'M', cart: [] as unknown[]},
});

service_binding.createViewModel('user', userSignal, (u) => ({
  fullName: `${u.firstName} ${u.lastName}`,
  cartIsEmpty: u.cart.length === 0,
}));
Note

setupBindDirectives() takes a required boolean — setupBindDirectives(false) if you call bootstrapDirectives() yourself afterward (the usual case, alongside the rest of your Flux setup), true if you want it to bootstrap immediately.

The bind directives

AttributeBehavior
bind_text="ns.prop"Sets textContent; a nullish value renders as an empty string.
bind_value="ns.prop"Sets an input's .value — write-guarded (skips the write if the DOM value already matches) so the caret position survives re-binding while typing.
bind_attrib="a=[!]ns.p; b=[!]ns.p"Toggles boolean attribute presence; a ! prefix negates. A nullish value removes the attribute; any other value is stringified.
bind_css_var="--var: ns.prop"Sets a CSS custom property on the element; a nullish value removes it.
html
<h2 bind_text="user.fullName">Loading...</h2>
<input type="text" bind_value="user.firstName" on-input="ui_edit_name:$value" />
<button bind_attrib="disabled=user.cartIsEmpty">Checkout</button>
<div bind_css_var="--player-progress: user.progress"></div>

lazy_bind

Add the lazy_bind flag attribute to defer a binding's subscription and first update until the element enters the viewport — the same IntersectionObserver machinery directives use for lazyInit_().

html
<div bind_text="user.fullName" lazy_bind></div>

The registry

service_binding owns every view model namespace:

  • .createViewModel(namespace, sourceSignal, project) — registers a namespace, backed by a ComputedSignal internally.
  • .getViewModel(namespace) — returns the underlying computed signal, or null if unregistered.
  • .removeViewModel(namespace) — destroys the computed signal and unregisters the namespace.