State

state() allows you to store and work with stateful values in your reactive flows:
  /*!*/import { wrap, map, state, pipe } from '@connectv/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

let a = document.getElementById('a') as HTMLInputElement;
let p = document.getElementById('p');

/*!*/let s = state();

wrap(fromEvent(a, 'input'))      //--> when something is typed
  .to(map(() => a.value))        //--> map it to the input value
  .to(pipe(debounceTime(1000)))  //--> wait for the typing to finish
/*!*/  .to(s.input);                  //--> set the state

//
// this will emit only when the state changes
// so you cannot have something like
//
// > state change: hellow
// > state change: hellow
//
/*!*/s.output.subscribe(v => p.innerHTML += '> state change: ' + v + '<br>');
state()s support implicit connection, so you could shorten the code above like this:
import { wrap, map, state, pipe } from '@connectv/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

let a = document.getElementById('a') as HTMLInputElement;
let p = document.getElementById('p');

wrap(fromEvent(a, 'input'))      //--> when something is typed
  .to(map(() => a.value))        //--> map it to the input value
  .to(pipe(debounceTime(1000)))  //--> wait for the typing to finish
  .to(state())                  //--> set the state
  .subscribe(v => p.innerHTML += '> state change: ' + v + '<br>');

Late subscription

state() emits its value to late subscribers as well:
  import { wrap, map, state, pipe } from '@connectv/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

let a = document.getElementById('a') as HTMLInputElement;
let btn = document.getElementById('btn');
let p = document.getElementById('p');
let p2 = document.getElementById('p2');

let s = state();

wrap(fromEvent(a, 'input'))      //--> when something is typed
  .to(map(() => a.value))        //--> map it to the input value
  .to(pipe(debounceTime(1000)))  //--> wait for the typing to finish
  .to(s)                         //--> set the state
  .subscribe(v => p.innerHTML = 'state: ' + v);

/*!*/wrap(fromEvent(btn, 'click'))
/*!*/  .subscribe(() => {
/*!*/    //
/*!*/    // so this guy subscribes on the state well after
/*!*/    // the latest value was emitted to it, but still
/*!*/    // receives the latest value:
/*!*/    //
/*!*/    s.subscribe(v => p2.innerHTML = 'received: ' + v).unsubscribe();
/*!*/  });

Initial value

You can provide an initial value to state() as well:
  import { wrap, map, state } from '@connectv/core';
import { fromEvent } from 'rxjs';

let i = document.getElementById('i') as HTMLInputElement;
let p = document.getElementById('p');

wrap(fromEvent(i, 'input'))
  .to(map(() => i.value))
  .to(map(x => x * 2))
/*!*/  .to(state(42))             //--> so lets start with 42
  .subscribe(v => p.innerHTML = v);

Accessing state value

You can read the latest value of a state() or set it manually via .value property:
  import { state } from '@connectv/core';
import { fromEvent } from 'rxjs';

let i = document.getElementById('i') as HTMLInputElement;
let btn = document.getElementById('btn');
let d = document.getElementById('d');

//
// --> so lets display a list tof stuff that we can add to and remove from
//

let stuff = state([]);

stuff.subscribe(list => {                     // --> when list updates
  d.innerHTML = '';                           // --> KIDS, this is why we have vdom
  list.forEach(thing => {
    let div = document.createElement('div');  // --> create a div for each thing
    div.innerText = thing.text;               // --> show its `.text`
    fromEvent(div, 'click').subscribe(() => { // --> when the div is clicked
/*!*/      stuff.value = 
/*!*/        stuff.value.filter(t => t !== thing); // --> remove the thing
    });

    d.appendChild(div);                       // --> yeah add the div to document
  });
});

fromEvent(btn, 'click').subscribe(() => {     // --> when the 'Add' button is clicked
/*!*/  stuff.value = 
/*!*/    stuff.value.concat([{ text: i.value }]);  // --> add a thing
  i.value = '';
});

Change detection

state() only changes its value and emits when it receives a value that is deeply different:
  import { source, state } from '@connectv/core';

let a = source();

a.to(state()).subscribe(console.log);

/*!*/a.send([1, 2, 3]);
/*!*/a.send([1, 2, 3]);    //--> this one does not get logged
/*!*/a.send([1, 2, 3, 4]);
You can change this behavior by providing a custom equality function:
  import { source, state } from '@connectv/core';

let a = source();

/*!*/a.to(state((x, y) => x == y)) //--> provide custom equality check
  .subscribe(console.log);

/*!*/a.send([1, 2, 3]);
/*!*/a.send([1, 2, 3]);                //--> this is logged too
/*!*/a.send([1, 2, 3, 4]);

Signature

Each state() has an input pin named "value" and an output pin named "value", which are also accessible via shortcut properties .input and .output respectively.
let s = state();

s.in("value") == s.input;
s.out("value") == s.output;


Further reading




Copied to Clipboard!