A computed property is a value that derives from one or more other state properties. Instead of manually updating a derived value every time its inputs change, you register it once and Sprincul keeps it current. Computed values are available on this.state just like regular state, so your binding callbacks and other methods can read them without any special handling.
The addComputedProp() method
Call this.addComputedProp(name, fn, dependencies) from inside beforeInit() to register a computed property.
addComputedProp(name, fn, dependencies)
| Parameter | Type | Description |
|---|
name | string | The property name under which the computed value is available on this.state |
fn | () => any | A function that returns the computed value. Called with the model as this. |
dependencies | string[] | State property names that, when changed, trigger a recompute and DOM re-render |
The computed value is available immediately after registration via this.state.<name>.
Dependencies and DOM re-renders
The dependencies array tells Sprincul which state properties this computed value depends on. When any dependency changes, Sprincul invalidates the computed value and schedules a DOM update for elements bound to it.
Calling addComputedProp() without a dependencies array (or with an empty array) means the computed value will be accessible on this.state, but bound elements will not re-render when the value changes. Always provide dependencies when you need the DOM to stay in sync.
Registering computed properties from beforeInit()
You must call addComputedProp() from inside beforeInit(). This is because Sprincul needs the computed property registered before it sets up bindings, and beforeInit() is the hook that runs at that point. Calling addComputedProp() outside beforeInit() — before the core is available — will throw an error.
A full example: Totals
The Totals model computes a running total from price and qty. Both state properties are initialized in beforeInit(), and the computed total property is registered with both as dependencies. When either input changes, Sprincul recomputes total and updates the bound <span>.
<div data-model="Totals">
<label for="price">Price</label>
<input id="price" name="price" type="number" value="10" oninput="setPrice" />
<label for="qty">Quantity</label>
<input id="qty" name="qty" type="number" value="2" oninput="setQty" />
<p>Total: <span data-bind-total="showTotal"></span></p>
</div>
// Totals.js
import { SprinculModel } from 'sprincul';
export default class Totals extends SprinculModel {
beforeInit() {
// Initialize state
this.state.price = 10;
this.state.qty = 2;
// Register a computed property that depends on price and qty
this.addComputedProp(
'total',
() => this.state.price * this.state.qty,
['price', 'qty']
);
}
/** @param {InputEvent} e */
setPrice(e) {
this.state.price = Number(e.target.value || 0);
}
/** @param {InputEvent} e */
setQty(e) {
this.state.qty = Number(e.target.value || 0);
}
/** @param {HTMLElement} el */
showTotal(el) {
el.textContent = String(this.state.total);
}
}
When the user changes either input:
- The event handler updates
this.state.price or this.state.qty.
- Sprincul detects the dependency change and schedules an update for
total.
- On the next animation frame,
showTotal is called with the bound <span>.
this.state.total returns the freshly computed value (price * qty), and the element’s text is updated.
Batching behavior
Computed property invalidations run in the same requestAnimationFrame batch as regular state updates. If you change multiple dependencies in the same synchronous block, the computed property recomputes once and the DOM updates once — not once per dependency change. This keeps rapid updates predictable and avoids redundant work.
// Both changes are batched — showTotal is called once, not twice
this.state.price = 20;
this.state.qty = 5;
Computed properties work the same way as regular state when it comes to bindings. Use data-bind-total="showTotal" exactly as you would for any other state property.