Toggle a value and initialize it from a stream with RxJS

This week I faced a somehow simple issue while coding an RxJS/CycleJS application: How to toggle a value and set its initial value via an observable. The use-case is the following one: I have a value that I want to toggle to enable or disable a feature of the application. However, I want the default value to be configurable via a URI parameter.

So, I can start my application without parameter in the URI:

http://localhost:8080

In this case, I want my toggle value to be initialized with a default value. But I can also start my application with a parameter to initialize my toggle value:

http://localhost:8080?mode=foo

Surprisingly, I did not find any example of how to implement this.

Toggle a value in RxJS

Let's first see how to toggle a value. A toggle implementation is straightforward thanks to the scan operator:

const toggle$ = Rx.Observable.fromEvent(toggleEl, 'click')
  .map( e => {
    return { 'what': 'toggle'};
  });

const toggle = toggle$
  .scan( (state, v) => {
    return !state;
  }, false)
  .subscribe( (v) => {
    console.log("Value changed to " + v);
});

In this example, toggleEl is a DOM reference to the button that can be used to toggle the value. Each time the button is clicked, a 'toggle' event is fired on the toggle stream. Then the value of state is toggled in the scan operator. In this example, the default value is set to false. Now the question is how to set this default value from an observable?

Initialize the Toggle value from an observable

In the previous example two variables were used: the toggle variable, and the default value variable. We now have to introduce a third variable: An initialization variable that can be provided during the application startup. So we now have another stream that allows to set the initial value of the toggle:

// in real life, the init stream comes from an async source 
const init$ = Rx.Observable.from([true]);

const toggleInit$ = init$
  .map( e => {
    return { 'what': 'initialize', 'value': e};
})

The toggleInit stream will send at most one event to initialize the toggle value. We can use it the following way in the scan operator: The accumulator holds the accumulated value (the toggle state) and the information whether this value has been initialized. such a very simple state machine allows to use either a default value, either a provided initialization value when the first toggle event fires. Once the first toggle event is fired, any initialization event is ignored. Here is the implementation:

const toggle = toggle$.merge(toggleInit$)
  .scan( (acc, v) => {
    if(v.what == 'toggle') {
      acc.state = !acc.state;
    } else if(v.what == 'initialize') {
      if(acc.isRunning == false) { // guard: ignore these events once initialized
        acc.state = v.value;      
      }
    }
    
    if(acc.isRunning == false) // accumulator is initialized on reception of first event
      acc.isRunning = true;
    
    return acc;
  }, { 'isRunning': false, 'state': true}) // state contains the default value
  .map( acc => acc.state)
  .subscribe( (v) => {
    console.log("Value changed to " + v);
  });

Conclusion

The complete example is available in this fiddle. Note that with this code no event is fired before an initialization value is provided or the toggle button is clicked. This may or may not be an issue depending on what you need. This solution should also be applicable to any other ReactiveX implementation.

They posted on the same topic

Trackback URL : https://blog.oakbits.com/index.php?trackback/47

This post's comments feed