ActiveJS has 4 fundamental types of constructs, Units, Systems, Action, and Cluster. All these constructs are Observables since they extend RxJS' Observable class.
You don't need to learn all of them to start using ActiveJS; Units and Actions can be used independently without any further understanding of Systems or Clusters. Also, ActiveJS is tree-shakeable, hence the parts that you don't use, will not get bundled in your build.
This data-flow diagram might give you some perspective before we dive into details. It shows how ActiveJS is connected with other parts of a Web app. All parts of an App can communicate with any instance of an ActiveJS construct directly, there is no abstraction layer involved like a Store, Dispatcher, Middleware, or Reducer.
Every state management solution relies on a robust storage system, and for that exact purpose, ActiveJS has independent, reactive storage Units. Simply called Units.
Units emulate JavaScript's native data structures. There's a specialized type of Unit for each of the most used native data structures. All the Units are also Observables.
BoolUnitis a boolean counterpart, it stores and provides a boolean value at all times.
NumUnit is a number counterpart, it stores and provides a number value at all times.
StringUnit is a string counterpart, it stores and provides a string value at all times.
ListUnitis an array counterpart, it stores and provides an array value at all times.
DictUnitis loosely based on Map, it stores and provides a simple object value at all times.
GenericUnit doesn't pertain to any specific data type, it's generic in nature, it can store any type of value.
// initialize a UnitconstmessageUnit=newStringUnit(); // StringUnit has default-value ''// listen for the valuesmessageUnit.subscribe(value =>console.log(value));// logs '' immediately, and will log 'hello' after next step// dispatch a new valuemessageUnit.dispatch('hello'); // StringUnit only acccepts strings// directly access valueconsole.log(messageUnit.value()); // logs 'hello'
Reactive storage Systems help with async APIs like XHR and fetch or any third party abstraction.
A System is just a systematic combination of one or more Units with pre-configured inter-relationships.
For example, AsyncSystem can be used to store, share, replay, and wait for async tasks. This is realized by utilizing four Units for every aspect of an async task, it includes threeGenericUnits, one each for query, response and error, and oneBoolUnit for the pending-status of an async task.
// initialize a System for fetching and sharing user dataconstuserDataSystem=newAsyncSystem();// extract the Units, that are part of the AsyncSystemconst {queryUnit,dataUnit,errorUnit,pendingUnit} = userDataSystem;// using destructuring assignment// a function to fetch data and disptch the response appropriatelyasyncfunctionfetchAndShareData(query) {try {// fetch data using fetch APIconstresponse=awaitfetch('https://xyz.com/u/'+query.userId);// extract the JSON dataconstdata=awaitresponse.json();// dispatch data to the dataUnit, it also sets the pendingUnit's value to false// and it also clears the errorUnit's valuedataUnit.dispatch(data); } catch (err) {// dispatch error to errorUnit, it also sets the pendingUnit's value to falseerrorUnit.dispatch(err); }}// setup the stream by observing query values, that triggers fetchAndShareData// whenever a value is dispatched to queryUnitqueryUnit.subscribe(query =>fetchAndShareData(query));
Our setup is complete, now we can easily share query, data, error and pending-status with different parts of our App.
// listen for queriesqueryUnit.subscribe(query =>console.log(query));// listen for datadataUnit.subscribe(data =>console.log(data));// listen for errorserrorUnit.subscribe(error =>console.log(error));// listen for pending statuspendingUnit.subscribe(isPending =>console.log(isPending));
Now, all we need to do is trigger a new API request, which also can be done from anywhere, like this:
// dispatch a query, it'll also set pendingUnit's value to true// the rest will be handled by the stream we just created abovequeryUnit.dispatch({userId:42069});
Actions are meant to represent unique reactive events. Action is an RxJS Subject like construct, it also extends RxJS Observable, and implements custom features like replay on demand.
Actions help trigger customized operations in a reactive fashion.
// initialize an Actionconstnotifier=newAction();// every time the Action emits, we alertnotifier.subscribe(message =>alert(message));// not executed on subscription, waits for a dispatch// we can dispatch any value from anywherenotifier.dispatch('Say Sike');// replay the last valuenotifier.replay(); // will emit 'Say Sike' again// directly access valueconsole.log(notifier.value()); // logs 'Say Sike'
A Cluster is a wrapper of two or more Units, Systems, Actions, or even Clusters. It's intended to be used to create a group of several ActiveJS instances.
It creates a master Observable of combined values of provided members of the Cluster.
It also provides direct access to its members and their combined value.
// create a few Units to combineconstnumUnit=newNumUnit(); // with default value 0conststrUnit=newStringUnit(); // with default value ''constlistUnit=newListUnit(); // with default value []// create a ClusterconstmyPrecious=newCluster({numUnit, strUnit, listUnit})// using shorthand notation// static value accessconsole.log(myPrecious.value())// and reactive value access, emits whenever a memeber emitsmyPrecious.subscribe(value =>console.log(value));// both will immediately log the following{ numUnit:0, strUnit:'', listUnit: []}// accessing the Unit through the Clusterconsole.log(myPrecious.items.numUnit.value()); // logs 0