The name “Observable” immediately indicates a relation to the Observer pattern. This pattern is key in many flavors of the Model-View-Controller architectural pattern for user interfaces. For instance, typically the View observes changes in the Model. In the Observer pattern, this means the Model would be the “Subject” being observed.
However, an Observable is not exactly the same concept as a traditional Subject from the Observer pattern, because Observables share features with Iterables from the Iterator pattern as well.
Observables are lazy event streams which can emit zero or more events, and may or may not finish.
Observables originated from ReactiveX, a Reactive Programming library. Reactivity is an important aspect in Cycle.js, and part of the core principles that led to the creation of this framework. There is a lot of confusion surrounding what Reactive means, so let’s focus on that topic for a while.
Say you have a module Foo and a module Bar. A module can be considered to be an object of an OOP class, or any other mechanism of encapsulating state. Let’s assume all code lives in some module. Here we have an arrow from Foo to Bar, indicating that Foo somehow affects state living inside Bar.
A practical example of such arrow would be: whenever Foo does a network request, increment a counter in Bar. If all code lives in some module, where does this arrow live? Where is it defined? The typical choice would be to write code inside Foo which calls a method in Bar to increment the counter.
Because Foo owns the relationship “when network request happens, increment counter in Bar”, we say the arrow lives at the arrow tail, i.e., Foo.
Bar is passive: it allows other modules to change its state. Foo is proactive: it is responsible for making Bar’s state function correctly. The passive module is unaware of the existence of the arrow which affects it.
The alternative to this approach inverts the ownership of the arrow, without inverting the arrow’s direction.
With this approach, Bar listens to an event happening in Foo, and manages its own state when that event happens.
Bar is reactive: it is fully responsible for managing its own state by reacting to external events. Foo, on the other hand, is unaware of the existence of the arrow originating from its network request event.
What is the benefit of this approach? Inversion of Control, mainly because Bar is responsible for itself. Plus, we can hide Bar’s
incrementCounter() as a private function. In the passive case, it was required to have
incrementCounter() public, which means we are exposing Bar’s internal state management outwards. It also means if we want to discover how Bar’s counter works, we need to find all usages of
incrementCounter() in the codebase. In this regard, Reactive and Passive seem to be duals of each other.
|How does Bar work?||Find usages||Look inside|
On the other hand, when applying the Reactive pattern, if you want to discover which modules are affected by an event in a Listenable module, you must find all usages of that event.
|Which modules are affected?||Look inside||Find Usages|
Passive/Proactive programming has been the default way of working for most programmers in imperative languages. Sometimes the Reactive pattern is used, but sporadically. The selling point for widespread Reactive programming is to build self-responsible modules which focus on their own functionality rather than changing external state. This leads to Separation of Concerns.
The challenge with Reactive programming is this paradigm shift where we attempt to choose the Reactive/Listenable approach by default, before considering Passive/Proactive. After rewiring your brain to think Reactive-first, the learning curve flattens and most tasks become straightforward, especially when using a Reactive library like RxJS.
Reactive programming can be implemented with: event listeners, Bacon.js, Kefir, EventEmitter, Actors, and more. Even spreadsheets utilize the same idea of the cell formula defined at the arrow head. The above definition of Reactive programming is not limited to RxJS, and does not conflict with previous definitions of Reactive Programming. RxJS was chosen as the reactive tool in Cycle.js.
There is also an ongoing effort by the TC39 committee to insert the
Observable, making Cycle.js a very lightweight framework.
In short, an Observable is an event stream which can emit zero or more events, and may or may not finish. If it finishes, then it does so by either emitting an error or a special “complete” event.
As an example, here is a typical Observable: it emits some events, then it eventually completes.
Observables can be listened to, just like EventEmitters and DOM events can.
Notice there are 3 handlers: one for events, one for errors, and one for “complete”. You can omit the last two in case you are only interested in handling “onNext” events.
RxJS Observables become very useful when you transform them with pure functions, creating new Observables on top of existing ones. Given an Observable of click events, you can make an Observable of “double-click” events.
Knowing the basics of ReactiveX is a prerequisite to getting work done with Cycle.js. Instead of teaching RxJS on this site, we recommend a few great learning resources, in case you need to learn more:
- The introduction to Reactive Programming you’ve been missing: a thorough introduction to RxJS by Cycle.js author Andre Staltz.
- Introduction to Rx: an online book focused on Rx.NET, but most concepts map directly to RxJS.
- ReactiveX.io: official cross-language documentation site for ReactiveX.
- Learn Rx: an interactive tutorial with arrays and Observables, by Jafar Husain.
- RxJS lessons at Egghead.io
- RxJS GitBook
- RxMarbles: interactive diagrams of RxJS operators, built with Cycle.js.
Observables in Cycle.js
Now we are able to explain the types of
actuators, and what it means for the computer and human to be “mutually observed.”
In the simplest case, the computer generates pixels on the screen, and the human generates mouse and keyboard events. The computer observes these user inputs and the human observes the screen state generated by the computer. Notice that we can model each of these as Observables:
- Computer’s output: a screen Observable.
- Human’s output: an Observable of mouse/keyboard events.
While doing the same with the
human() function can exist, in practice, we need to use driver functions in order to reach the external world.
Drivers are adapters to the external world, and each driver represents one aspect of external effects. For instance, the DOM Driver takes a “screen” Observable generated by the computer, and returns Observables of mouse and keyboard events. In between, the DOM Driver function produces “write” side effects to render elements on the DOM, and catches “read” side effects to detect user interaction. This way, the DOM Driver function can act on behalf of the user.
Joining both parts, we have a computer function, often called
main(), and a driver function, where the output of one is the input of the other.
The circular dependency above cannot be solved if
= means assignment, because that would be equivalent to the command
x = g(f(x)), and
x is undefined on the right-hand side.
This is where Cycle.js comes in: you only need to specify
domDriver(), and give it to the Cycle.js
run() command which connects them circularly.
This is how the name “Cycle.js” came to be. It is a framework that solves the cyclic dependency of Observables which emerge during dialogues (mutual observations) between the Human and the Computer.
What is Cycle Core?
Cycle Core delivers only the
run()function which is implemented in about 100 lines of code. It’s a very small library.
In the TodoMVC built with Cycle.js, these are the proportions of code each library or section comprises:
- RxJS: 71,1%
- virtual-dom: 9,76%
- @cycle/dom: 4,87%
- src: 2,47%
- @cycle/core: 0,64%
- misc: 10,77%
Cycle.js is simply an architecture for building reactive web applications: a set of ideas about how you should structure your app using RxJS. To help you out, it also provides some libraries to address common use cases: Cycle DOM, to help interact with the DOM, and Cycle Core, to help create loops between the program and the drivers.
Read, next, the basic examples, which applies what we’ve learned so far about Cycle.js.