Documentation
Signal

Signal

Signal follows the reactive programming paradigm. It allows the creation of signal values, which are objects that emit values over time, and provides methods for observing and manipulating them.

Signal values are similar to variables in that they store data. However, unlike variables, signal values can change over time. Signal values are triggered by mutating them, and when they are triggered, they emit a new value. These values can be transformed and combined using operators. Operators allow for the creation of more complex data flows. By chaining together operators, it is possible to handle a wide range of use cases. Signal values can eventually be observed to call side effect functions.

Usage Example

Let's start by defining a signal. The signal has two properties: is_checked (initially false) and text (empty string), which we'll use later.

local n = require("nui-components")
 
local renderer = n.create_renderer({
  width = 60,
  height = 12,
})
 
local signal = n.create_signal({
  is_checked = false,
  text = "",
})
 
local body = function()
  return n.rows()
end
 
renderer:render(body)

Now, let's use n.checkbox to create a row with a checkbox component. This checkbox will allow us to display a text input component. The checkbox has the following properties: autofocus (set to true), label, value (bound to the is_checked property of the signal). When you press the checkbox (using <CR> or <Space>), we update the is_checked property of the signal.

local n = require("nui-components")
 
local renderer = n.create_renderer({
  width = 60,
  height = 12,
})
 
local signal = n.create_signal({
  is_checked = false,
  text = "",
})
 
local body = function()
  return n.rows(
    n.checkbox({
      autofocus = true,
      label = "Display text_input component",
      value = signal.is_checked,
      on_change = function(is_checked)
        signal.is_checked = is_checked
      end,
    })
  )
end
 
renderer:render(body)

Let's create another row. This time, we'll have two nested rows. Both rows will be hidden when the is_checked property of the signal is false. The first row contains a text input component with the following properties: flex (set to 1), value (bound to the text property of the signal). When the user types something, we update the text property of the signal.

local n = require("nui-components")
 
local renderer = n.create_renderer({
  width = 60,
  height = 12,
})
 
local signal = n.create_signal({
  is_checked = false,
  text = "",
})
 
local body = function()
  return n.rows(
    n.checkbox({
      autofocus = true,
      label = "Display text_input component",
      value = signal.is_checked,
      on_change = function(is_checked)
        signal.is_checked = is_checked
      end,
    }),
    n.rows(
      {
        flex = 1,
        hidden = signal.is_checked:negate(),
      },
      n.text_input({
        flex = 1,
        value = signal.text,
        on_change = function(value)
          signal.text = value
        end,
      })
    )
  )
end
 
renderer:render(body)

Next, let's use n.columns to create the second row with two columns. The first column fills all available horizontal space. The second column contains a button component. When you press the button, we clear the text input.

local n = require("nui-components")
 
local renderer = n.create_renderer({
  width = 60,
  height = 12,
})
 
local signal = n.create_signal({
  is_checked = false,
  text = "",
})
 
local body = function()
  return n.rows(
    n.checkbox({
      autofocus = true,
      label = "Display text_input component",
      value = signal.is_checked,
      on_change = function(is_checked)
        signal.is_checked = is_checked
      end,
    }),
    n.rows(
      {
        flex = 1,
        hidden = signal.is_checked:negate(),
      },
      n.text_input({
        flex = 1,
        value = signal.text,
        on_change = function(value)
          signal.text = value
        end,
      }),
      n.columns(
        { flex = 0 },
        n.gap({ flex = 1 }),
        n.button({
          label = "Clear",
          on_press = function()
            signal.text = ""
          end,
        })
      )
    )
  )
end
 
renderer:render(body)

And here's the final result:

Methods

observe

The observe method allows you to keep track of changes that occur in a signal. You can also specify a debounce delay. To prevent any memory leaks, it's important to remember to call the unsubscribe method when you no longer need to observe the changes.

:observe(next_fn, debounce_ms?)
Parameters
next_fnfun(previous_state: table, current_state: table): nil
debounce_ms?number
Returns
Subscription

get_value

Returns the current value of the Signal object.

:get_value()
Returns
table

Methods / Operators (Signal Value)

get_value

Returns the current value of the corresponding key from the Signal object.

:get_value()
Returns
T

get_observer_value

Returns the current value returned by the observer subscription.

:get_observer_value()
Returns
T

get_observable

Returns an observable that emits the values of the corresponding key in the Signal object over time.

:get_observable()
Returns
Observable

map

Applies a mapping function to each value emitted by the observable.

:map(map_fn)
Parameters
map_fnfun(value: T): R
Returns
SignalValue

negate

Negates the emitted value using a mapping function.

:negate()
Returns
SignalValue

debounce

Delays an operation until a specified time has passed without further input.

:debounce(ms)
Parameters
msnumber
Returns
SignalValue

tap

Executes a function with each value emitted by the observable.

:tap(tap_fn)
Parameters
tap_fnfun(value: T): nil
Returns
SignalValue

filter

Filters the values emitted by the observable using a predicate function.

:filter(pred_fn)
Parameters
pred_fnfun(value: T): boolean
Returns
SignalValue

skip

Skips the first n values emitted by the observable.

:skip(n)
Parameters
nnumber
Returns
SignalValue

combine_latest

Combines the latest values from multiple SignalValue objects (using the provided arguments as keys).

:combine_latest(..., combinator_fn)
Parameters
...SignalValue[]
combinator_fnfun(...): T
Returns
SignalValue

scan

Applies a scan function to each value emitted by the observable and accumulates the result into an internal variable, returning the final value.

:scan(scan_fn, initial_value)
Parameters
scan_fnfun(acc: T, value: R): T
initial_valueT
Returns
SignalValue

start_with

Emits a default value immediately and synchronously after subscribing.

:start_with(default_value)
Parameters
default_valueT
Returns
SignalValue

observe

Subscribes an observer function to the observable. The observer function is called every time a new value is emitted by the observable.

:observe(on_next)
Parameters
on_nextfun(value: T): nil

unsubscribe

Unsubscribes the observer subscription from the observable, cancelling any further updates.

:unsubscribe()

dup

Returns a new SignalValue object with a reference to the same underlying subject and key.

:dup()
Returns
SignalValue