reactive.coffee

Side Effects and Resource Cleanup

Sometimes you will have side effects associated with certain binds or components, such as establishing a connection or setting an interval timer:

blinkingBox = (opts) ->
  active =  rx.cell false
  setInterval (-> active.set not active.get()), opts.ms ? 500
  div {class: bind -> if active.get() then 'red-box' else 'black-box'}

$('body').append(
  div {class: 'container'}, boxes.map -> blinkingBox()
)

In these cases, you'll want to do proper resource management/cleanup whenever the bind context is removed or refreshed—in this case, stopping the interval timer. For these situations, you can use rx.onDispose(callback):

blinkingBox = (opts) ->
  active =  rx.cell false
  interval = setInterval (-> active.set not active.get()), opts.ms ? 500
  rx.onDispose -> clearInterval(interval)
  div {class: bind -> if active.get() then 'red-box' else 'black-box'}

Garbage Collection

Dependencies hold references to their dependents in order to know who to propagate updates to. We do currently unsubscribe a dependent observable from its dependencies whenever that dependent is refreshed, but if the dependent observable itself creates nested binds, those nested binds must also be disconnected, and so on, recursively.

For instance, say we have:

showUnread = rx.cell(true)
unread = rx.cell(0)
div {class: 'scoreboard'}, bind -> [
  if showUnread.get()
    div {class: 'unread'}, bind -> "#{unread.get()} message(s)"
  else
    '(nothing shown)'
]

Initially, unread has a subscriber, as expected: the inner bind. When we update showUnread, the outer bind will re-evaluate and create a new inner bind (for the nested div), completely forgetting about the old one. Without the nested bind cleanup, unread now has two subscribers, old and new inner binds. And since the old inner bind is still referenced from unread's list of subscribers, it can't be garbage-collected. Ergo, memory leak.

What instead happens: bind tracks any nested binds. On re-evaluation, these inner binds are disconnected, unsubscribing themselves from all their dependencies. This disconnection also occurs recursively down to their nested binds, and so on.

One question is what happens at the top level or when you want to break out and do your own thing. We don't have the luxury of weak refs in JS, and as a result it's less forgiving if you do "silly" things like create binds that go nowhere and that you don't want to keep.

(But even with weak references, one can't tell if you added a bind only to subscribe a console.log caller to its changes, save to localStorage, or some other side effect that you do want to keep.)

For instance, you don't want to write the above as the following:

showing = false
$('.toggle-button').click ->
  if showing
    $('.scoreboard').html(
      div {class: 'unread'}, bind -> "#{unread.get()} message(s)"
    )
  else
    $('.scoreboard').text('(nothing shown)')
  showing = not showing
}

The key for these situations is that binds should go all the way to the top, or else you'll want to manually disconnect your (top-level) binds using their disconnect method:

showing = false
topBind = null
$('.toggle-button').click ->
  if showing
    $('.scoreboard').html(
      div {class: 'unread'}, topBind = bind -> "#{unread.get()} message(s)"
    )
  else
    $('.scoreboard').text('(nothing shown)')
    topBind.disconnect()
  showing = not showing
}

Mutations Inside Binds

Say you had some dependent cell:

plusOne = bind -> src.get() + 1

If you try something like the following, i.e. explicitly wire up a dependent cell to modify a source cell:

bind ->
  x = plusOne.get()
  dst.set(val)
src.set(2)

you'll get a warning that you're trying to perform a cell-mutating operation within the subgraph of a dependent cell.

This is discouraged because a well-structured program should exhibit a clean DAG of dependencies. When you do something like this, you're potentially creating cycles. It also muddies what dst should reflect, when you have multiple bind locations calling dst.set(...)—it's often clearer to express the value of dst as some expression of its dependencies.

Usually you'll instead want to have dst be a dependent cell of plusOne rather than be its own SrcCell:

dst = bind -> plusOne.get()

However, in case you need an escape hatch in a bind (har har), you can always set up a manual listener:

plusOne = bind ->
  src.onSet.sub rx.skipFirst ([old, val]) -> val + 1
  ...

We need skipFirst since by default the listener is immediately invoked with the current value of src.

Note that dst will not be considered a dependency of src or plusOne. This connection is unmanaged (or rather, managed by you).

So if this is happening within some bind context, you must also unsubscribe on context disposal:

uid = src.onSet.sub rx.skipFirst ([old, val]) -> dst.set(val)
rx.onDispose -> src.onSet.unsub(uid)

There is a convenience function, autoSub, which will take care of this for you:

rx.autoSub src.onSet, rx.skipFirst ([old, val]) -> dst.set(val)

Asynchronous Binds

So far we've seen synchronous binds. However, let's say you want to have introduce a delay between when a bind's dependencies are updated and when that bind is evaluated. This can be useful, for instance, if you have an expensive bind over some dependencies, where the dependencies can change in bursts of high frequency, and you don't want the app to block on re-evaluating the bind every time the dependencies change. Similarly, you may want a UI component to update in response to the user entering text into a text box, but you don't want a jarring user experience and want to instead only update the UI once the user stops typing for some time.

There is in fact a lagBind:

y = rx.lagBind 500, 0, -> expensiveFunction(x.get())

Here, y isn't immediately evaluated every time x changes; rather, y (which is initialized to 0) waits for x to settle down for at least half a second before evaluating the expensive function of x.

There's also a similar function, postLagBind, contributed by @eizenberg, which instead introduces a lag but after the function evaluation/re-capture (which is immediate upon any changes to dependencies). This allows you to specify a dynamic delay as part of the result, where the delay duration comes from observables. In this example, the tootip appears instantly, but disappears only after a delay:

showTooltip = rx.postLagBind false, ->
  if focused.get() and showTooltips.get() then val: true, ms: 0
  else val: false, ms: 300

More generally, you can implement your own asynchronous binds. bind, lagBind, and postLagBind are all expressed in terms of asyncBind. You supply a function that runs the asynchronous lifecycle. At a single point in the lifecycle, you can call this.record(f), passing it a function which will record and subscribe to dependencies. This can be called only once; otherwise things like disposal of prior subscriptions gets very complex and multiple in-flight lifecycles gets more complex (if you need multiple stages of bindings separated by asynchronous gaps, you can use a chain of multiple asynchronous binds). Upon completion, this.done(x) must be called with the result.

Here's an example that immediately evaluates/records its dependencies and then feeds that to an AJAX call; upon completion, the result is passed into @done.

y = rx.asyncBind 0, ->
  input = @record -> x.get()
  ajaxCall({data: input, done: (output) => @done(output)})

Users interested in asyncBind are also encouraged to take a look at the (very short) implementations of bind, lagBind, and postLagBind.

ES5 Properties

So far, everything is built on observable data structures, which are just plain classes with explicit getter/setter calls. To make working with observables more natural/less verbose as well as more similar to working with regular non-observable data structures, we can use ES5 object property getters/setters, so that what used to be:

card = deck.cards.at(deck.cards.length() - 1)
card.isFlipped.set(not card.isFlipped.get())

becomes:

card = deck.cards[deck.cards.length - 1]
card.isFlipped = not card.isFlipped

Your class definitions are simpler as well. You can rewrite this:

class Deck
  constructor: (player, cards) ->
    @player = rx.cell(player)
    @cards = rx.array(cards)
class Card
  constructor: (suit, rank, isFlipped) ->
    @suit = rx.cell(suit)
    @rank = rx.cell(rank)
    @isFlipped = rx.cell(isFlipped)

as:

class Deck
  constructor: (@player, @cards) ->
    rx.autoReactify(this)
class Card
  constructor: (@suit, @rank, @isFlipped) ->
    rx.autoReactify(this)

In short, you can construct "plain" objects, then autoReactify them, which replaces all fields with ES5 getters/setters (coupled with hidden backing observable cells/arrays).

However, there are a few caveats and limitations, primarily around arrays. Arrays are implemented by actually replacing the methods on normal Arrays with wrappers that also call the appropriate method on the observable. However, there is no way to override the indexing/subscript operator [] in JS. As a result, normal indexing operations may be less efficient (this will likely be fine for most arrays/binds in your application, however, unless they're really big):

deck.player # translates into deck.player.get()
deck.cards # translates into deck.cards.all()
deck.cards[0] # translates into deck.cards.all()[0], not deck.cards.at(0)
deck.cards.length # translates into deck.cards.all().length

# so, these won't work react to changes
cards = deck.cards
bind -> cards[0]
bind -> cards.length

# instead, always access as a field:
bind -> deck.cards[0]
bind -> deck.cards.length

All things considered, we still recommend using normal observable arrays over these magical ES5 array proxies, given the limitations. When you call autoReactify on an object, it will essentially ignore and leave intact any existing observables.

Thanks to @m1sta for bringing this idea to the table.