πŸ’Ύ
Units
Units are the most important part of ActiveJS, they are responsible for holding the state of your single page application. Units can also be used independently, without any further knowledge of ActiveJS.
Two way data-flow between View Components and a shared Unit.
Units are specialized, reactive data structures based on JavaScript's native data structures like boolean, string or array.
For example, ListUnit is an elaborate array like data structure which is also an Observable and implements all the Array.prototype methods in a way that, any mutation caused by these methods, emits a new array with the mutations applied to it.
The actual value is stored inside the Unit and can be accessed via the value() method.
All the available Units are BoolUnit, NumUnit, StringUnit, ListUnit, DictUnit, and GenericUnit.

β–Ά Initializing a Unit

A Unit has several configuration options, including an optional initial-value.
1
const unit1 = new NumUnit();
2
// OR
3
const unit2 = new NumUnit({initialValue: 0});
4
// OR
5
const unit3 = new NumUnit({initialValue: 5, cacheSize: 10});
Copied!
Every type of Unit has a designated default value. NumUnit has 0 as the default value, and StringUnit has ''. That's why in the above example the unit1 and unit2 are equivalent.

πŸ“‘ Accessing the value

1
// static access
2
console.log(unit1.value()); // logs 0
3
​
4
// reactive access
5
unit1.subscribe(value => console.log(value)) // logs 0, will log future values
6
​
7
// reactive non-replay access
8
unit1.future$.subscribe(value => console.log(value)) // will only log future values
Copied!
As you might've noticed, when subscribing to the unit1, a value immediately gets logged to the console even though we haven't dispatched any value yet. If you are only interested in future values you can use future$ Observable, which doesn't replay the last value on subscription, it only emits future values.

πŸ“‘ Dispatching a value

All Units ignore any invalid value dispatch, Units only accept their designated value type, except the GenericUnit, because it's designed to accept and store all types of values.
1
// initialize
2
const numUnit = new NumUnit(); // value is 0
3
​
4
// dispatch a new value
5
numUnit.dispatch(42); // returns true for successful dispatch
6
// value is 42 now
7
​
8
// using dispatch method with a value-producer function
9
numUnit.dispatch(currentValue => currentValue + 1);
10
// value is 43 now
11
​
12
// invalid dispatch
13
numUnit.dispatch('a string'); // this will be ignored, returns false
14
// value is still 43
Copied!
There are two ways to dispatch a new value. You can pass a value or a value-producer-function to generate a new value to the dispatch method, the dispatch method returns true or false depending on the success of dispatch, which you can use to make any further decisions.

πŸ”™ Using the Cached values

All Units cache 2 values by default, it allows us to go back and forth through these cached-values in such a way that it doesn't affect the cached-values, only the current-value changes. The caching mechanism works almost exactly like browser history.
The cache can be utilized in many ways, but probably the most common use case would be undo and redo capabilities. For undo feature we can use goBack method and for redo we can use goForward method respectively.
To dive into more details, see Caching to understand how it works.
1
// create a Unit with default cache-size of 2
2
const unit = new StringUnit({initialValue: 'a'});
3
​
4
// cache is ['a']
5
​
6
unit.goBack(); // won't work, returns false
7
unit.goForward(); // won't work, returns false
8
​
9
unit.dispatch('b'); // cache becomes ['a', 'b']
10
console.log(unit.value()) // logs 'b'
11
​
12
unit.goBack(); // cache is still ['a', 'b']
13
console.log(unit.value()) // logs 'a'
14
​
15
unit.goForward(); // cache is still ['a', 'b']
16
console.log(unit.value()) // logs 'b'
17
​
18
// we can also access all the cached values
19
console.log(unit.cachedValues()) // logs ['a', 'b']
20
​
21
// or check the current cache index
22
console.log(unit.cacheIndex) // logs 1
23
​
24
// or check the count of cached values
25
console.log(unit.cachedValuesCount) // logs 2
Copied!

β†Ί Clear & Reset

Resetting a Unit to it's initial-value is as easy as calling a method. Similarly clearing the value is also that easy.
1
// create a Unit
2
const unit = new NumUnit({initialValue: 69});
3
​
4
// clear the value
5
unit.clearValue(); // now value is 0 (the default value for NumUnit)
6
​
7
// reset the value
8
unit.resetValue(); // now value is 69 again (the initial-value)
Copied!

πŸ’Ž Immutability

To demonstrate immutability we'd need a different kind of Unit because the NumUnit deals with a primitive type number which is already immutable.
Let's take a ListUnit to create a reactive, array like data structure.
1
// initialize a immutable ListUnit.
2
const randomList = new ListUnit({immutable: true});
3
// ListUnit has default initial value []
4
​
5
// subscribe for the value
6
randomList.subscribe(value => console.log(value));
7
// logs [] immediately and will log future values
Copied!
We just created an immutable Unit, that's all it takes, a configuration flag.
βœ” Mutation check
1
const anItem = {type: 'city', name: 'Delhi'};
2
randomList.push(anItem);
3
// this push is reactive, it'll make the Unit emit a new value
4
​
5
// let's try mutation by reference
6
anItem.type = 'state'; // this would work
7
// but the value of the randomList won't be affected, because every time
8
// a value is provided to an immutable list,
9
// it's cloned before storing.
10
​
11
// let's try another approach
12
const extractedValue = randomList.value(); // get the current value
13
console.log(listValue); // logs [{type: 'city', name: 'Delhi'}]
14
// try to mutate the extractedValue
15
extractedValue[1] = 'let me in...'; // this would work
16
// but the value of the randomList won't be affected, because every time
17
// an immutable list provides a value, it's cloned,
18
// to destroy all references to the stored value.
Copied!
See Immutability guide for more details.

βš“ Persistence

To make a Unit persistent, all we need is a unique id so that the Unit can identify itself in the localStorage, and a configuration flag.
1
// initialize
2
const persitentUnit = new StringUnit({id: 'userName', persistent: true});
3
// StringUnit has default inital value ''
Copied!
That's it, this StringUnit is persistent, it already saved its default value to localStorage.
βœ” Persistence check
1
// let's dispatch a new value different than the default value to
2
// properly test the persistence
3
persitentUnit.dispatch('Neo');
4
console.log(persitentUnit.value()); // logs 'Neo'
5
​
6
// now if we re-initialize the same Unit
7
// on second initialization the Unit will restore its value from localStorage
8
// e.g: after a window refresh
9
console.log(persitentUnit.value()); // logs 'Neo'
Copied!
See Persistence guide for more details.

πŸ” Replay and Replay-ness

Every Unit immediately provides the value when subscribed, by default, but maybe you only want the future values. For that purpose, every Unit has a built-in alternative Observable that doesn't emit immediately on subscription.
1
const unit = NumUnit(); // NumUnit has default initialValue 0
2
​
3
// normal subscription
4
unit.subscribe(v => console.log(v)) // immediately logs 0
5
​
6
// future only subscription
7
unit.future$.subscribe(v => console.log(v)) // doesn't log anything
8
​
9
// both will log any future values
10
unit.dispatch(42); // you'll see two 42 logs in the console
Copied!
You can also turn the default replay-ness off.
1
const unit = NumUnit({replay: false});
2
// now default Observable and future$ Observable are the same
3
​
4
// normal subscription
5
unit.subscribe(v => console.log(v)) // doesn't log anything
6
​
7
// future only subscription
8
unit.future$.subscribe(v => console.log(v)) // doesn't log anything
9
​
10
// both will log any future values
11
unit.dispatch(42); // you'll see two 42 logs in the console
Copied!
By default, every Unit behaves like a BehaviorSubject, but unlike a BehaviorSubject Units don't require an explicit default value, since a Unit already has a default value.

πŸ”‚ Manual Replay

Imagine a Unit is being used as a source for an API request, and you have a "refresh" button to trigger the request again. For this and many other scenarios, Units provide a manual replay method.
1
const unit = StringUnit({initialValue: 'Alpha'});
2
​
3
unit.subscribe(v => /*make API request*/); // send every value to the server
4
​
5
unit.dispatch('Sierra'); // send another value
6
​
7
// to emit the same value again, all you have to do is
8
unit.replay();
9
// all subscribers will get the same value again, in this case, 'Sierra'
10
// so the server should receive 'Alpha', 'Sierra', 'Sierra'
Copied!

❄ Freezing

If you want a Unit to stop accepting new values, in scenarios where the state is not supposed to change. All you need to do is this:
1
// create a Unit
2
const unit = DictUnit(); // a DictUnit has default value {}
3
​
4
// freeze the Unit
5
unit.freeze();
6
​
7
// this will be ignored
8
unit.dispatch({'nein': 'nein nein'})
9
// so will any other mutative, or cache-navigation methods
10
// like goBack(), goForward(), clearValue(), resetValue() etc.
11
​
12
// unfreeze the Unit, and everything will start working again
13
unit.unfreeze();
Copied!
See Freezing guide for more details.

πŸ”‡ Muting

If you want a Unit to stop emitting new values, but keep accepting new values, in scenarios where you aren't interested in new values but still don't want to lose them. All you need to do is mute the Unit.
1
// create a Unit
2
const unit = GenericUnit(); // a GenericUnit has default value undefined
3
// it accepts all kinds of values as the name suggests
4
​
5
// mute the Unit
6
unit.mute();
7
​
8
// this will work
9
unit.subscribe(value => console.log(value));
10
// logs undefined immediately, but will not log any new values until unmuted
11
​
12
// this will still work
13
unit.dispatch('Hello'); // but no subscriber will get triggered
14
​
15
// but if you check the value, you'll get still get the updated value
16
console.log(unit.value()); // logs 'Hello'
17
​
18
// unmute the Unit, and if the value changed while the Unit was muted,
19
// emit it to all the subscribers, to bring them in sync
20
unit.unmute();
Copied!
See Muting guide for more details.

πŸ“… Events

Every Unit emits an event for every operation performed on it, you can tap into these events to take some other action.
1
// create a Unit
2
const listUnit = new ListUnit();
3
​
4
// subscribe to events
5
listUnit.events$.subscribe(event => console.log(event));
Copied!
There's an event for almost every operation that can be performed on a Unit, for example:
1
// a succefull dispatch
2
listUnit.dispatch([69]); // will emit EventUnitDispatch
3
// an invalid dispatch
4
listUnit.dispatch({}); // will emit EventUnitDispatchFail
5
// on freeze
6
listUnit.freeze(); // will emit EventUnitFreeze
7
// on ListUnit specific methods
8
listUnit.push("Hard"); // will emit EventListUnitPush with value "Hard"
9
// another example
10
listUnit.pop(); // will emit EventListUnitPop
11
// and so on...
Copied!
See Events guide for more details.

πŸ›  Units vs native data structures

Units can not be used as drop-in replacements for native data structures. However, in most scenarios, a Unit instance can be used directly instead of Unit.value()
Every Unit implements valueOf and toString methods. JavaScript automatically invokes these methods when encountering an object where a primitive or string value is expected, respectively. Hence NumUnit, StringUnit, and BoolUnit can be used similar to their primitive counterparts.
Additionally, Units also implement their counterparts prototype methods like NumUnit implements Number.prototype methods to make it easier to work with the stored value. Similarly, ListUnit implements all the Array.prototype methods, StringUnit implements all the String.prototype methods, and so on.

number vs NumUnit​

1
const num = 42069;
2
const numUnit = new NumUnit({initialValue: 42069});
3
​
4
num.toString() // '42069'
5
numUnit.toString() // '42069'
6
​
7
num.toLocaleString() // '42,069' (in an 'en' locale)
8
numUnit.toLocaleString() // '42,069' (in an 'en' locale)
9
​
10
num + 1 // 42070
11
numUnit + 1 // 42070 // this doesn't change the Unit's value
12
​
13
num + 'XX' // '42069XX'
14
numUnit + 'XX' // '42069XX' // this doesn't change the Unit's value
Copied!
array vs ListUnit​
1
const arr = ['πŸ‘½', 'πŸ‘»'];
2
const listUnit = new ListUnit({initialValue: ['πŸ‘½', 'πŸ‘»']});
3
​
4
arr.toString() // 'πŸ‘½,πŸ‘»'
5
listUnit.toString() // 'πŸ‘½,πŸ‘»'
6
​
7
arr.join('--') // 'πŸ‘½--πŸ‘»'
8
listUnit.join('--') // 'πŸ‘½--πŸ‘»'
9
​
10
arr.push('πŸ€–') // mutates the same array
11
listUnit.push('πŸ€–') // this is reactive, creates and dispatches a new array
12
​
13
// ListUnit is also iterable
14
[...arr] // a shallow copy of arr ['πŸ‘½', 'πŸ‘»']
15
[...listUnit] // a shallow copy of stored value ['πŸ‘½', 'πŸ‘»']
16
​
17
// and every Unit works with JSON.stringify
18
JSON.stringify({num, arr}) // '{"num":42069, "arr": ["πŸ‘½", "πŸ‘»"]}'
19
JSON.stringify({numUnit, listUnit}) // '{"numUnit":42069, "listUnit": ["πŸ‘½", "πŸ‘»"]}'
Copied!
There are even more cases where you can treat a Unit just like a native data structure, barring a few exceptions like ListUnit and DictUnit don't have key-based property access and assignment, they use get and set methods instead. And the fact that BoolUnit, NumUnit or StringUnit aren't actually primitives, the typeof operator will only return object.

More Examples

1
const numUnit = new NumUnit({initialValue: 3});
2
// current value is 3
3
​
4
numUnit + 1 // 4
5
// is same as this
6
numUnit.value() + 1 // 4
7
​
8
numUnit + 'idiots' // '3idiots'
9
// is same as this
10
numUnit.value() + 'idiots' // '3idiots'
11
​
12
JSON.stringify({n: numUnit}) // {n: 3}
13
// is same as this
14
JSON.stringify({n: numUnit.value()}) // {n: 3}
15
​
16
​
17
// that's pretty much it,
18
// in other situations the Unit will behave like an object, as it should
19
numUnit === 3 // false
20
typeof numUnit === 'object' // true
21
numUnit++ // won't work, it will try to replace numUnit const with number 4
22
​
23
// similarly
24
​
25
const boolUnit = new BoolUnit({initialValue: false});
26
​
27
boolUnit + '' // 'false'
28
boolUnit + 1 // 1, because false is converted to 0
29
boolUnit.dispatch(true);
30
boolUnit + 1 // 2, because true is converted to 1
31
​
32
// similarly
33
​
34
const strUnit = new StringUnit({initialValue: 'Hello'});
35
​
36
strUnit + ' World' // 'Hello World'
37
strUnit + 1 // 'Hello1'
38
strUnit.dispatch('6');
39
parseInt(strUnit) // 6
Copied!

πŸ“Š Units vs BehaviorSubject

​
Unit
BehaviorSubject
Replays value on subscription
βœ…
βœ…
Can be configured to not replay value on subscription
βœ…
❌
Allows to listen to only future values
βœ…
❌
Accepts an initial-value
βœ…
βœ…
Can cache more than one value
βœ…
❌
Allows cache-navigation
βœ…
❌
Validates dispatched values
βœ…
❌
Specialized for specific data structures
βœ…
❌
Can re-emit/replay last value manually
βœ…
❌
Can be frozen
βœ…
❌
Can be muted
βœ…
❌
Can be immutable
βœ…
❌
Can be persistent
βœ…
❌
Can be reset
βœ…
❌
Can be part of a Cluster​
βœ…
❌

Configuration Options

The configuration options can be passed at the time of instantiation. All the configuration options are optional. You can set them per Unit or you can also set most of them globally. See Configuration for more details.
Last modified 1yr ago