Why use reactive streams for components?

How exchanging a set of streams solves component communication.

TL;DR - the parent component passes an object of reactive streams to the child component

When working with a web framework, a parent component often needs to pass a property that will change over time into a child component. Typically, the template-based frameworks use special syntax for specifying the parent to child property mapping. Whenever the property value changes in the parent component, it will "magically" update in the child component too.

Let me list how several modern frameworks specify this mapping before showing how reactive frameworks can implement the same feature in more a powerful way.

Mapping properties from parent to child in Angular 1

For example Angular 1 uses directives to map properties.

1
2
3
4
5
6
7
8
9
angular.directive('person', function() {
return {
restrict: 'E',
scope: {
firstName: '=first'
},
templateUrl: 'person.html'
};
});

Notice that the property scope.firstName in the child (directive) is mapped to whatever will be specified as attribute first in the HTML of the parent component. The parent component gives the name of its own property to be passed. In this case the parent's manager.ownName is mapped to the child's property firstName via the HTML tag attribute first.

1
2
3
4
<!-- parent template -->
<div>
<person first="manager.ownName"></person>
</div>

The communication between the parent thus requires 3 things

  • the parent component lists which property of its own (manager.ownName) to map to the child component "bind name", in this case first.
  • the child component looks under this "bind name" and maps the passed attribute to its own local name firstName.
  • this map is "live" - every time the parent updates manager.ownName, the child will have a new value in firstName. This is implemented perhaps less than efficiently (but very simply) using the dirty checking algorithm during the digest cycle.

One time binding

What if the child is only interested in the initial property value? In that case there is a slightly different notation. The parent component selects to pass only the initial value of its own property.

1
2
3
4
<!-- parent template -->
<div>
<person first="::manager.ownName"></person>
</div>

Source: ONE-TIME BINDINGS IN ANGULAR 1.3

To the child directive person the property still looks the same. Yet the parent will only pass the first value and will cut the connection. This improves the performance, yet makes it hard for ensure that the right initial value is passed. Consider a case when the name is fetched from the server. If we never assign anything to the expression manager.ownName, then the right value is passed to the person component.

1
2
3
4
5
6
7
8
.controller('parent', ($scope, $http) => {
$http.get('/name')
.then(name => {
$scope.manager = {
ownName = name
}
})
})

But if we assign something to the $scope.manager before HTTP call, for example for clarity, then that initial value will be passed to the child. The value received from the HTTP call will become the second and will NOT make it to the child component person.

1
2
3
4
5
6
7
8
9
10
11
.controller('parent', ($scope, $http) => {
$scope.manager = {ownName: undefined}
// the child component will only receive `undefined`
// and not the fetched "second" value
$http.get('/name')
.then(name => {
$scope.manager = {
ownName = name
}
})
})

If we really wanted a flexible communication system, the child component should control when to receive the values from the parent, and when to unsubscribe.

Mapping values in Vue.js

Before we consider this convoluted system an Angular-specific problem, here is the same two way property binding in Vue.js

The custom directive has to declare every expected property, and the parent component has to pass the name of its own property, just like in Angular 1, but with more explicit parts.

See Passing data with props

1
2
3
Vue.component('person', {
props: ['first']
})
1
2
3
4
<!-- parent template -->
<div>
<person v-bind:first="manager.ownName"></person>
</div>

For one time binding (a rare case in Vue.js because there is no dirty checking performance penalty), there is v-once directive, but it does not really give you much in this case.

To solve the HTTP async case and only display the second changed value, one can "watch" the child's data property and keep the track of the change number manually.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// inside the child component
var gotFirstCount = 0
{
data: () => ({
// our own property
name: undefined
}),
props: ['first'], // passed from the parent
watch: {
first: function (val) {
if (gotFirstCount < 2) {
this.name = val
gotFirstCount += 1
}
}
}
}

We have to write a lot more boilerplate code if we want to be flexible with multiple values passed into the component.

Mapping values in React / JSX

React and its template language JSX requires very little custom syntax, yet a "simple" logic like "only the first non-empty string" value requires the imperative "if / else" statement, implemented using boolean expressions.

1
2
3
4
5
6
7
8
// inside the parent component
// this.props.manager = {ownName: ...}
return <person first={props.manager.ownName}></person>
// inside the "person" component
return (
{props.first && <div>name {props.first}</div>}
{!props.first && <div>loading ...</div>}
)

I find this style a slight improvement, yet also imperative and confusing.

Property passing using reactive programming

The above approaches all used custom syntax to map a property name from the parent object to make it accessible as a property in the child object. Yet, the solutions are opaque and only deal well with "live" data that flows from the parent to the child, without much control over the data flow by the child object.

What I would like to do instead:

  • replace framework-specific "magic" properties with clear explicit types.
  • make the mapping from the parent property to the child clear and easy to see.
  • allow the child component to fully control how it listens to the value updates, or when it stops listening.

Here is my solution (which is similar to the way Cycle.js enabled the communication between its components).

A parent component passes an object of properties it allows the child component to "use". Only it is not a "plain" object, but every property value is a reactive stream.

That's it.

If the child needs every value of the parent's property, it just subscribes to the updates. The code looks something like this:

1
2
3
4
5
6
function person(parent) {
const {manager: {ownName}} = parent
ownName.subscribe((name) => {
console.log('person got new name', name)
})
}

Here is the entire code with "child", "parent" components and their connection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const Rx = require('rxjs/Rx')

// child "component" definition
function person(parent) {
const {manager: {ownName}} = parent
ownName.subscribe((name) => {
console.log('person got new name', name)
})
}

// parent creates its data
const parent = {
manager: {
ownName: Rx.Observable.of(undefined, 'Joe')
}
}

// create child "component"
// note that we have full control over which properties
// we allow the child to access
const child = person(parent)

/*
output:
person got new name undefined
person got new name Joe
*/

If the child needs only the first value of the parent's property, it can efficiently get it without any custom template syntax, using regular Rx operator. The change is 1 word.

1
2
3
4
5
6
7
8
9
// child "component" definition
function person(parent) {
const {manager: {ownName}} = parent
ownName
.first() // or .take(1)
.subscribe((name) => {
console.log('person got new name', name)
})
}

Let us test this

1
2
3
4
5
6
7
// parent creates its data
const parent = {
manager: {
ownName: Rx.Observable.of('Joe', 'Mary', 'Ann')
}
}
// person got new name Joe

What if we want to reject all invalid names, and only take the first non-empty string? Pass a predicate to the first() method.

1
2
3
4
5
6
7
8
9
10
11
12
function person(parent) {
const {manager: {ownName}} = parent
ownName
.first(s => s) // <= right here!
.subscribe((name) => {
console.log('person got new name', name)
})
}
/*
parent stream [undefined, '', 'Mary'] produces
person got new name Mary
*/

What if we want to be more performant and avoid multiple child messages if the name passed from the parent does not change?

1
2
3
4
5
6
// parent creates its data
const parent = {
manager: {
ownName: Rx.Observable.of('Joe', 'Mary', 'Mary', 'Mary', 'Ann')
}
}

Again, this is a data flow problem, which is solved using a regular Rx operator

1
2
3
4
5
6
7
8
function person(parent) {
const {manager: {ownName}} = parent
ownName
.distinctUntilChanged() // <= right here!
.subscribe((name) => {
console.log('person got new name', name)
})
}

Output shows only the changes

1
2
3
person got new name Joe
person got new name Mary
person got new name Ann

Pretty cool, and if the stream passes an immutable data structure, the effect in the .subscribe function (printing to the console, rendering to the DOM) becomes very efficient since it is executed only for if the data has changed.

Conclusion

With the above approach we have the following advantages

Simplicity

We can avoid framework-specific unnatural textual description of the complex runtime data flow that maps one property to another.

No need to invent <parent name-will-be-camel-cased="foo-bar" /> weird intermediate HTML-compatible grammars. The explicit library operator s.first() is always going to be simpler to understand and remember than the framework-specific ::name="property" notation.

Power

The child component gets more power over the data flow from the parent

The child can grab every / first / distinct / first passing predicate value from the parent.

Isolation

the parent component can still have control over what gets passed into the child.

For example, the parent component can create a stream of the first value when passing into the child component

1
2
3
4
5
6
7
8
9
10
// data stream with everything
const parent = {
manager: {
ownName: Rx.Observable.of('Joe', 'Mary', 'Ann')
}
}
// child component can only get the first value
const child = person({
manager: {ownName: parent.manager.ownName.first()}
})

Performance

The application can get to the high performance mark because only the running subscriptions are processed

If the child stopped listening after the first passed value by using .first() the parent stream will be freed automatically.

Addendum 1

Once one is passing properties as objects full of reactive streams, one can derive a user-friendly API to simplify basic cases. I prefer passing an object with properties because it is simple to destruct. The only improvement would be to automatically wrap primitive values as Observables.

In the code below we recursively walk the properties object and convert any non-object to an observable of a single value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const R = require('ramda')
// parent only has static data
const parent = {
manager: {
ownName: 'Joe'
}
}
// create child "component"
// pass primitive data as streams
const child = person(observe(parent))
// the utility API function 'observe' ensures all
// primitives are converted to Observables
// before being passed to the child function
function observe(what) {
const toObservable = (value, key) =>
R.is(Object, value) ? observe(value) : Rx.Observable.of(value)
return R.mapObjIndexed(toObservable, what)
}
/*
output:
person got new name Joe
*/

In another abstraction we could delay creating observables until they are requested by the child component. For example, we can pass an object that will only create an Observable when the child component calls .need(<path>).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const Rx = require('rxjs/Rx')
const R = require('ramda')

// child "component" definition
function person(parent) {
// ask only for a property by name
ownName = parent.need('manager.ownName')
ownName
.subscribe((name) => {
console.log('person got new name', name)
})
}

// parent only has static data
const parent = {
manager: {
ownName: 'Joe'
}
}

// create child "component"
const child = person(creator(parent))
function creator(data) {
return {
// returns a new Observable of a static value
need: path =>
Rx.Observable.of(R.path(path.split('.'), data))
}
}
/*
output:
person got new name Joe
*/

In a real system, the parent's data would already have Observable values and our .need(<path>) method would convert them into shared "hot" streams instead.

Addendum 2

The child component function is isolated from the parent environment, yet they can coexist and communicate via the "in-" stream. Because the child component "picks" the properties it uses by subscribing there is almost no performance penalty in passing more properties than necessary (there is a tiny overhead for creating an idle Observable). I would use the data encapsulation by the parent heuristic when deciding which properties to expose to other components.

Similarly, any component can be isolated from the DOM environment by creating a "wrapper" object that allows one to subscribe to the events. In this case the performance is even better because no one should create unnecessary streams in the first place.

1
2
3
4
5
6
7
8
9
const DOM = {
select: (what, eventName) => Rx.Observable.fromEvent($(what), eventName)
}
function person(dom) {
dom.select('body', 'clicks') // creates stream
.scan((count, e) => count + 1, 0) // "redux-lite" inside the stream
.subscribe(counter => console.log(`clicked ${counter} times`))
}
person(DOM)

Looking at the native DOM as a reactive stream API is a powerful abstraction.

Addendum 3

So far we have only seen the data flow in one direction - from the parent component function into the child component function. What if we want to return data from the child component? Notice that in all examples, we wrote variable name child and assigned the result of calling person(parent) to this variable, yet function person has never returned anything!

1
2
3
4
5
function person(parent) {
// no return statement
}
// create child "component"
const child = person(parent)

Since we are dealing with the real world, it makes sense for the child component to return multiple values. The way for a function to return multiple values is to return an Observable.

I prefer to return again an object with individual property values being Observables, to have symmetry with the input object full of reactive streams.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const Rx = require('rxjs/Rx')

// child "component" definition
function person(parent) {
const {manager: {ownName}} = parent
return {
reaction: ownName.map(name => `I like ${name}!`)
}
}

// parent creates its data
const parent = {
manager: {
ownName: Rx.Observable.of('Joe', 'Mary', 'Ann')
}
}

// create child "component"
// note that we have full control over which properties
// we allow the child to access
const child = person(parent)
child.reaction.subscribe(console.log)
/*
output:
I like Joe!
I like Mary!
I like Ann!
*/

A word of caution

What if you want the two components "talk to each other"? For example, you could wait for the "child" component's reaction before assigning a new manager. When manually pushing events around like this, it is important to remember when to stop and "complete" the stream.

To demonstrate, let us try making a simple Subject which is an Observable but allows us to push new events into it. When the reply from the child component arrives we will push the second value (and expect the child to reply with the second reaction text).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// child "component" definition
function person(parent) {
const {manager: {ownName}} = parent
return {
reaction: ownName.map(name => `I like ${name}!`)
}
}
// parent creates its data
const parent = {
manager: {
ownName: new Rx.Subject()
}
}
// create child "component"
// note that we have full control over which properties
// we allow the child to access
const child = person(parent)
child.reaction.subscribe(s => {
console.log(s)
parent.manager.ownName.next('Mary')
})
// start the ball rolling
parent.manager.ownName.next('Joe')
/*
output:
I like Joe!
I like Mary!
I like Mary!
...
RangeError: Maximum call stack size exceeded
*/

Hmm, we cannot have a reactive stream go into a loop, pushing new event over and over. We need to know when to stop! In this case we never sent complete event to the manager.ownName stream, thus we got into the infinite loop. We need to send the "complete" event after sending the data AND we need to do this asynchronously to allow the "complete" propagate, rather just the data event (getting into the infinite loop again).

1
2
3
4
5
6
7
8
9
10
11
12
child.reaction.subscribe(s => {
console.log(s)
process.nextTick(() => {
parent.manager.ownName.next('Mary')
parent.manager.ownName.complete()
})
})
/*
output:
I like Joe!
I like Mary!
*/

When connecting streams into loops, it is important to remember that the stream cannot form a cycle, thus there has to be a "breaker circuit" that stops the same event from traveling in circles.

Related resources

  • Mobx is the closest implementation to the above approach, but with non-Rx terms (instead using terms like observer, reactions). Definitely check it out.
  • Cycle.js is a reactive framework that takes modeling everything with streams and isolating components seriously.
  • Rxjs is a large library of reactive streams with many many operators. Two good alternatives are xstream and most.