Deep States

You can use deep(state(...)) for situations when you need to be able to treat indices or properties of a state() as a reactive states as well:
  /*!*/import { state, deep } from '@connectv/core';

/*!*/let s = deep(state([1, 2, 3, 4]));    // --> create a deep state

/*!*/s.sub(2).subscribe(console.log);      // --> subscribe on index 2

                                      // > 3 (initial value)
s.value = [1, 5, 6, 7, 8];            // > 6
s.value = [8, 5, 3]                   // --> no log since no emission
s.value = [true, false];              // > undefined
s.value = ['hellow', 'world', 'wow']; // > wow
In this example, s.sub(2) is a reactive state whose value is index 2 on s, i.e. its a sub-state of s on index 2. You can similarly create sub-states on properties of an object:
  import { state, deep } from '@connectv/core';

let s = deep(state({
  name: 'John',
  address: {
    city: 'Munich',
    street: 'Theresien St.'
  }
}));

/*!*/s.sub('address')
/*!*/ .sub('street').subscribe(console.log);  // --> subscribe on `address.street`

s.value = {
  name: 'John',
  address: {
    city: 'Munich',
    street: 'Leopold St.'
  }
};

Two-way data flow

By default, sub-states only receive data from their parent state, i.e. data only down-propagates from parent to child. You can enable up-propagation, i.e. causing a child state to also update parent state's value, by calling .bind() on the sub-state:
  import { state, deep } from '@connectv/core';

let s = deep(state({
  name: 'John',
  address: {
    city: 'Munich',
    street: 'Theresien St.'
  }
}));

s.subscribe(console.log);        // --> log changes of parent state

/*!*/let name = s.sub('name').bind(); // --> get `.name` as a bound sub-state
/*!*/name.value = 'Jack';             // --> change value of `.name`
Note that for up-propagating from grand-child states (for example from a property-chain), you would need to bind every sub-state down from the parent state:
  import { state, deep } from '@connectv/core';

let s = deep(state({
  name: 'John',
  address: {
    city: 'Munich',
    street: 'Theresien St.'
  }
}));

s.subscribe(console.log);          // --> log changes of parent state

/*!*/let city = s.sub('address').bind() // --> get `.address` and bind it
/*!*/            .sub('city').bind();   // --> get `.city` and bind it
city.value = 'Berlin';             // --> update `.city`

Keyed states

In some cases, it might be more interesting to track entities regardless of their position in a collection. For example, if you have a list such as the following:
let users = [
  {
/*!*/    id: 47391,
    name: 'John',
    age: 23
  },
  {
/*!*/    id: 59370,
    name: 'Jill',
    age: 32
  },
  {
/*!*/    id: 10400,
    name: 'Jar Jar',
    age: 20
  }
];
You might be more interested in having a sub-state reflecting the age of Jar Jar instead of age of the third person in the list, as the third person in the list might not necessarily be Jar Jar or you might not care about his position in the list, just his age (and even if you do, it is a separate concern).

The important distinction in such a case is that your logic needs to identify entities in the collection not by their position in the collection but through some other manner, e.g. id field in the case of above example. In that case, you can provide a key function to deep(), which it will then use to identify entities with, and you can access a particular entity with a particular identifier value using the .key() method:
  import { state, deep } from '@connectv/core';

let s = deep(state([
  {
    id: 47391,
    name: 'John',
    age: 23
  },
  {
    id: 59370,
    name: 'Jill',
    age: 32
  },
  {
    id: 10400,
    name: 'Jar Jar',
    age: 20
  }
]), 
/*!*/ u => u.id);                       // --> the key function

/*!*/ let jarjarAge = s
/*!*/                 .key(10400)      // --> Jar Jar is id 10400 ...
/*!*/                 .sub('age');     // ... lets look at his age ...
jarjarAge.subscribe(console.log); // ... and log it

s.value = [
  {
    id: 10400,
    name: 'Jar Jar',
    age: 21
  },
  {
    id: 39104,
    name: 'Joseph',
    age: 71
  },
];

Two-way data flow

Similar to normal sub-states, keyed sub-states by default only receive values from parent state. You can similarly enable up-propagation of data by calling .bind() on them:
  import { state, deep } from '@connectv/core';

let s = deep(state([
  {
    id: 47391,
    name: 'John',
    age: 23
  },
  {
    id: 59370,
    name: 'Jill',
    age: 32
  },
  {
    id: 10400,
    name: 'Jar Jar',
    age: 20
  }
]), 
/*!*/u => u.id);

s.subscribe(console.log);

/*!*/let jarjarAge = s.key(10400).bind()
/*!*/                 .sub('age').bind(); // --> enable up-propagation
/*!*/jarjarAge.value = 24;                // --> so this causes parent to 
/*!*/                                     // ... reemit with updated jar jar age

Index tracking

On a keyed deep state you can also track index changes of a particular entity using .index() method:
  import { state, deep } from '@connectv/core';

let s = deep(
          state([{name: 'Jack'}, {name: 'Jill'}]), 
          u => u.name
        );

/*!*/s.index('Jack').subscribe(console.log);     // --> log index of 'Jack'
                                            // > 0  (initial index)
s.value = [{name: 'Jill'}, {name: 'Jack'}]; // > 1  (index after change)
s.value = [{name: 'Jill'}];                 // > -1 (index after removal)

Change detection

You can also track collection-wide changes on keyed deep states using .changes, which is a pin that emits when entities are added, removed or moved around in the collection:
  import { state, deep } from '@connectv/core';

let s = deep(
          state([{name: 'Jack'}, {name: 'Jill'}]), 
          u => u.name
        );

/*!*/s.changes.subscribe(console.log);

s.value = [{name: 'Jill'}, {name: 'Jack'}];                    // --> items being moved around
s.value = [{name: 'Joseph'}, {name: 'Jill'}, {name: 'Jack'}];  // --> item added
s.value = [{name: 'Joseph'}, {name: 'Jack'}];                  // --> item removed
Each emission of .changes is an object with three keys: NOTE that .changes is ONLY available on keyed deep states as the state requires the key function to be able to track entities in the collection independent of their indexes.

Further reading




Copied to Clipboard!