Skip to content

Signals

A signal holds a value that you can read and write. Use createSignal initialinside an active graph, or construct Signal (graph, initial)against an explicit graph.

createSignal returns the Signal<'T> itself. It is always ready: unlike an async source, a signal cannot be pending or failed.

Reading and writing

MemberMeaning
.ValueReads the value and records a dependency when a memo or effect is running
.Value <- xWrites the value
.PeekReads the value without recording a dependency
.TryValueReads the value as a Reading<'T> (Ready, Pending or Failed) and records a dependency
createSignal in FSharp
let count = createSignal 1
// Read
count.Value // 1
// Set
count.Value <- 2
// Read
count.Value // 2
count.Peek // 2
count.TryValue // Ready 2

Reading .Value or .TryValue inside a memo or effect records a dependency. Reading outside a computation simply returns the value. .Peek never records a dependency.

Use Signal.update count (fun value -> value + 1) to transform the current value. It reads through .Peek, so the update itself adds no dependency. The resulting write uses the same equality comparison as .Value <- value.

Propagation

A changed write marks direct observers dirty: they need to run again. Computations farther downstream are marked check: an upstream result might have changed. They bring their dependencies current before deciding whether their own bodies need to run. A mark for checking is not itself a re-run.

Effects normally finish before a write returns. Batch defers effects until the outermost batch ends; an unobserved memo refreshes only when read.

Equality

A Signal that does not change its value will not mark downstream observers:

The cutoff happens before notification. An equal write leaves the stored value and every observer unchanged. In comparison, AsyncSource.Settle always notifies its readers, even when the value is equal. A downstream memo may cut off that equal result after its own re-run, but its readers have already been marked for checking. See Async sources for the matching replay.

Choosing equality

GraphOptions.Equality supplies the comparison when each node is created:

  • JsIdentityPolicy is the default. Primitives and strings compare by value; reference types such as records and lists compare by identity on .NET.
  • StructuralPolicy uses EqualityComparer<'T>.Default.

An equal copy of a record therefore propagates under the default policy on .NET. Mutating an object in place and writing the same reference back is cut off. Prefer immutable updates that create a new value; see Deep and keyed updates.

Equality covers custom policies, NaN and differences between .NET and Fable. The policy is graph-wide; individual nodes do not accept their own comparers.

Lifetime and threads

A signal stores state; it does not own a computation or cleanup. Dispose the owner of its readers to stop their work and release dependency edges.

Writes obey the graph's thread-affinity guard, including equal writes. Use graph.Dispatch for writes from another thread; see Threading and dispatch.

Edit this page