OverviewGetting startedInstallationComponentsFoundationsCore and stateTheming and layoutAgent toolingAI toolingChange impactRecipesArchitectureRoadmapHelp and releasesGetting startedInstallationCore and stateGuidesRecipes

Signals

A signal holds a value; computed() derives one. Reads and writes are synchronous, and batch() groups writes so observers see one change.

TypeScript
import { batch, computed, signal } from '@oddkin/core/reactive';

const quantity = signal(1);
const price = signal(12);
const total = computed(() => quantity.value * price.value);

batch(() => {
  quantity.value = 2;
  price.value = 15;
});
console.log(total.value); // 30

The binding rule

  • Render-tracked reads. Reading .value while render() runs makes it a dependency: a change renders again.
  • Direct readable bindings. Passing the signal itself, ${this.count}, binds that one part: a change updates it without running render().
  • Untracked snapshots. Event handlers and asynchronous callbacks run later, outside the render: a .value read there is a snapshot and subscribes to nothing.
TypeScript
import { OddkinElement, define, html, signal } from '@oddkin/core';

class DemoCounter extends OddkinElement {
  private count = signal(0);
  private increment = () => this.count.update((value) => value + 1);

  override render() {
    // The text binding follows count; incrementing does not re-run render().
    return html`<button type="button" @click=${this.increment}>Count: ${this.count}</button>`;
  }
}

define('demo-counter', DemoCounter);

The settled wording, with what a template does with each kind of value, is the binding rule in core-contracts.md §1 and its binding-value matrix (§6.1).

Ownership with Scope

Each connection gives the element a Scope. Listeners, timers, effects, requests, and controllers registered on it end when the element disconnects, and start again from connected(scope) when it reconnects. There is no separate cleanup method to forget.