State & Derived
State is the reactive foundation of every Lyap scope. Declare it once, read and update it from actions and expressions, and every bound directive updates automatically.
Declaring state
Call scope.state({...}) inside a standard <script> block. Every property you declare is wrapped in a deep reactive proxy and is immediately accessible to HTML directives.
<div>
<script>
const userApp = Lyap.scope('userApp');
userApp.state({
count: 0,
firstName: 'Alex',
lastName: 'Smith'
});
</script>
<p>Hello, <span ly-text="userApp.firstName"></span>!</p>
<p>Count: <span ly-text="userApp.count"></span></p>
</div>
Count: · Double:
Derived values
Derived values are read-only properties computed from reactive state. They are lazy and memoized, updating only when their underlying signal dependencies change.
<div>
<script>
const profile = Lyap.scope('profile');
profile.state({
firstName: 'Alex',
lastName: 'Smith'
});
profile.derived({
fullName: () => `${profile.firstName} ${profile.lastName}`
});
</script>
<p>User: <span ly-text="profile.fullName"></span></p>
</div>
Hello, !
Read-only contract: Derived properties are computed on demand. Mutating a derived property directly is an error; mutate the upstream state instead.
Actions
Define action methods using scope.actions({...}). Action handlers execute with this bound to the reactive state proxy, allowing clean in-place mutations and async operations.
<div>
<script>
const counter = Lyap.scope('counter');
counter.state({ count: 0 });
counter.actions({
increment() {
this.count++;
},
reset() {
this.count = 0;
}
});
</script>
<p>Count: <span ly-text="counter.count"></span></p>
<button ly-on:click="counter.increment()">Increment</button>
<button ly-on:click="counter.reset()">Reset</button>
</div>
Safe sandboxed expressions
Directives evaluate sandboxed expressions without invoking eval() or new Function(). Property paths, arithmetic, ternary conditionals, and method invocations are parsed securely.
<p ly-text="app.count * 2"></p>
<p ly-text="app.fullName.toUpperCase()"></p>
<p ly-text="app.price > 100 ? 'expensive' : 'cheap'"></p>
<p ly-text="app.user.avatarUrl"></p>
<button ly-bind:disabled="app.isSaving || !app.isValid">Save</button>