Proxy

proxy() allows you to dynamically attach Agents to your reactive flows at a later stage.

For example, imagine we have a dynamic list of inputs (for example, inputs are added because of user action) and you need all your inputs to share the same state. For this purpose, we can use a composition like the following to manage the state of any given input:
import { wrap, map, state, pin,
      group, sink, composition } from '@connectv/core';
import { fromEvent } from 'rxjs';


//
// this composition manages the state of an html input element.
//
export const inputState = (input: HTMLInputElement) => composition(track => {
  let _in = pin(), _out = pin();

  let model = state();
  let toDom = sink(v => input.value = v);
  track(model, toDom);

  group(
    wrap(fromEvent(input, 'input')).to(map(() => input.value)), 
    _in
  ).to(model).to(toDom).to(_out);

  return [{ value: _in }, { value: _out }];
})();
Our inputState() acts like a state() that is also bound to the given input. We can bind each single inputState() to another state(), or bind multiple inputState()s to a single shared state(), so that all of them, well, share the state.

However, in our case, we do not have our inputs (and by extension, inputState()s) available at the time of defining the reactive flow that describes the binding. Instead, we can use a proxy() that is bound to the shared state, and when the inputState()s are created, use our proxy() to proxy the newly created agents into our flow:
/*!*/import { wrap, proxy, state } from '@connectv/core';
import { fromEvent } from 'rxjs';

import { inputState } from './input-state';

//
// GOAL: in this example, we want a dynamic number of inputs
// to have a shared state, and we should also have access to that
// state and for example display it in a <p> element.
//

let p = document.getElementById('p');
let btn = document.getElementById('btn');

//
//--> to ensure all inputs share the state, we create this
//--> 'shared' state so that we can bind all input states to it.
//
let shared = state();

//
//--> we do not have any inputs yet (so no `InputState`s as well),
//--> so we create a proxy for all of them.
//
/*!*/let inputProxy = proxy({ inputs: ['value'], outputs: ['value']});

/*!*/shared.output.to(inputProxy.in('value')); //--> now lets bind the proxy to the shared state
/*!*/inputProxy.out('value').to(shared.input); //... so that all `InputState`s will be bound to it

shared.output.subscribe(v => p.innerHTML = v); //--> also display the value of the shared state

fromEvent(btn, 'click').subscribe(() => {                      //--> upon click of the button...
  let i = document.createElement('input') as HTMLInputElement; //... create a new input
  i.setAttribute('type', 'text');                              //... set its type
  i.setAttribute('placeholder', 'type something ...');         //... set a placeholder
  document.body.appendChild(i);                                //... add it to the document

/*!*/  inputProxy.proxy(inputState(i));                         //--> and proxy its state
});
Lets unpack whats happening here:
let inputProxy = proxy({ inputs: ['value'], outputs: ['value'] });
shared.output.to(inputProxy.in('value'));
inputProxy.out('value').to(shared.input);
inputProxy.proxy(inputState(i));


Clearing up

In some cases, you might want to clear up some of the proxied agents of a proxy(), without clearing up the whole flow. The .proxy() method returns a subscription, unsubscribing from which will result in the proxied agent to be unproxied.

For example, in the case of previous example, imagine we would also want to dynamically remove inputs:
import { wrap, proxy, state } from '@connectv/core';
import { fromEvent } from 'rxjs';

import { inputState } from './input-state';


let p = document.getElementById('p');
let btn = document.getElementById('btn');

let shared = state();
let inputProxy = proxy({ inputs: ['value'], outputs: ['value']});

shared.output.to(inputProxy.in('value'));
inputProxy.out('value').to(shared.input);

shared.output.subscribe(v => p.innerHTML = v);

fromEvent(btn, 'click').subscribe(() => {
  let d = document.createElement('div');                       //--> lets wrap everything in a div

  let i = document.createElement('input') as HTMLInputElement;
  i.setAttribute('type', 'text'); 
  i.setAttribute('placeholder', 'type something ...');
  d.appendChild(i);

  let b = document.createElement('button');                    //--> so that we can add the remove
  b.textContent = 'remove';                                    //... button as well
  d.appendChild(b);

  document.body.appendChild(d);

/*!*/  let sub = inputProxy.proxy(inputState(i));
  fromEvent(b, 'click').subscribe(() => {
    d.remove();                                                //--> remove the div
/*!*/    sub.unsubscribe();                                         //--> also un-proxy
  });
});


Further reading




Copied to Clipboard!