Subscriptions

Subscriptions are a way to get values in your application from sources other than regular DOM node events (that you define directly in your views), like window events, timers, ajax calls and web sockets.

They exist in a pretty similar way in Elm and they are analogous to creating any event source in Cycle.js, just the workflow is very different.

Before understanding subscriptions, you should first take a look at actions and signals. Once you do that you will already know a bit about signals and their sources and processes. And subscriptions are nothing but signals. So if we wanna listen to the window's scroll, we should use the fromEvent source. Creating a source goes like this:

const source = fromEvent(window, 'scroll')

Note all DOM events have start and stop method, so when you pass then to your process, you have to call start. It exists because for some events, Act needs to stop the source, a DOM element that is removed from the DOM, for instance, so Act will call the method stop behind the scenes.

Now, there is a built in helper for processing the scroll position, called, conveniently, scroll. So you can just attach your source to it:

scroll(source.start())

When you create subscription you don't need to call the returning function yourself, you just pass it to Act and it will manage. The way to do that is to pass to Act's main method an object with the type of the action you want to trigger as the key. Here's the full code:

const subscriptions = {
  scroll: scroll(fromEvent(window, 'scroll').start())
}

main(view, { model, reducer, subscriptions })

With this code, Act will get the scroll position and send it to your reducer with an action like { type: 'scroll', payload: 123 } every time the scroll position changes.

In real life, you'll probably only case about certain specific scroll positions. To accomplish that you can simply put this logic in your reducer, but the encouraged way is to further compose your scroll process:

const moved = pipe(
  scroll,
  map((scrollPosition) => scrollPosition > 0)
  distinct
)

So in this process we first get the scroll position and turn it into a boolean, if the user has scrolled (>0). We finally call distinct, else we would get the true value everytime the user scrolled any pixel. distinct will filter our values to send them through only when they are different than the previous one (false becomes true and vice versa).

Another common usage here is to to throtlle (a.k.a. debouce) this event, so you don't get too many rerenders which is memory and time consuming. In order to emit the value only once in every second, just do:

const throttledScroll = pipe(
  throttle(1000),
  scroll
)

The cool thing about this is that these functions can be used not only for scroll but for any sort of subscription you need.

One final and relevant comment is that Ramda's functions are pretty handy for creating your own processes. Take a look at some ideas:

Ex Transforms
map(head) [1, 2, 3] => 1
'abc' => 'a'
map(tail) [1, 2, 3] => [2, 3]
'abc' => 'bc'
map(prop('name')) {name: 'Spinoza'} => 'Spinoza'
{} => undefined
map(propOr('Descartes', 'name')) {name: 'Spinoza'} => 'Spinoza'
{} => 'Descartes'
filter(equals(5)) 5 => 5
6 => ()
filter(propEq('id', 7)) {id: 7} => {id: 7}
{id: 8} => ()
filter(contains(100)) [99, 100] => [99, 100]
[99, 101] => ()
fold(add, 0) [1,2,3] => 6

Subscription helpers

Many use cases are too common for people to have to reinvent the wheel every time. If you followed the examples above, take a look at the subscriptions folder, and you'll find plenty of ready made helpers. Here are two of them:

const subscriptions = { breakpoint, scroll }

The breakpoint subscription will give you a breakpoint for the current screen width, every time it changes. You can provide custom breakpoint config using brekpointWith. And scroll will give you the current scroll position, throttled to a reasonable amount of time. To know more check the documentation on subscriptions.

Note that the examples above used the scroll helper from processes, so you can compose your own, and here scroll comes from the subscription helpers, a more high level library that abstracts the whole signal - source and process.

Subscriptions with side effects

Not all subscriptions are supposed to change our state. Some may only execute some side effect, or even do both. In this case, you can use the following syntax:

const subscriptions = [
  [sideEffect, subscription]
]

This will call sideEffect with the history and the subscription value every time there's a new value produced. Note this is analogous to actions with signals and side effects, even the syntax is the same.

If you want a quick way to know when to use each, look into the diagrams doc.