Showing posts with label rxjs. Show all posts
Showing posts with label rxjs. Show all posts

Sunday, November 24, 2019

Observer and subscriptions in JavaScript

Observers are widely used in JavaScript frameworks such as Angular, libraries like RxJS and others. That is why it is good to know what they do under the hood. The same principle is being successfully utilized by the JavaScript event listeners. More of the intriguing JavaScript aspects you can discover inside the JavaScript for beginners - learn by doing course.

In general, we have a list of subscribers that subscribe to certain events. When certain event emits data, all of its connected subscribers receive the data. The role of an observer is to observe and distribute events to proper subscribers. The simple action of subscribing to an event is referred to as a subscription.


Now to the code:

// we create a class: Observer
class Observer {
inside of its constructor we set up an initially empty list of subscribers.
constructor() {
this.subscribers = [];
}

// function subscribe, just adds subscriber to the subscribers' list
subscribe(subscriber) {
this.subscribers.push(subscriber);
}


// optional: unsubscribe from a particular event, we just remove a subscriber from the subscribers' array. Note that we need to pass as a parameter the whole subscriber object structure containing event and action, to be compared with the existing subscribers inside the this.subscribers array. This is because we would like to distinguish between multiple subscribers for the same event.
unsubscribe(subscriber) {
this.subscribers = this.subscribers.filter(subscriber => subscriber !== subscriber);
}


// inside the publish function, we do several things
publish(event, data) {
 // we search for those subscribers which are subscribed and respond to the same event as the passed function parameter (event)
this.subscribers
.filter(
subscriber =>
subscriber.event === event
)
// then for each list of the found subscribers we propagadate the data parameter i.e. send information to them
.forEach(
subscriber =>
subscriber.action(data)
);
}

}

Now let's see the observer usage in action:

// we first construct object out of our observer class: my_observer
const my_observer = new Observer();

// then we subscribe to event 1, we actually set up the event we will be listening to, as well as what kind of action to be performed when the event is received
my_observer.subscribe({
event: 'event 1',
action: (data) => {
console.log('received event 1', data);
}
});

// now to subscribe to another event
my_observer.subscribe({
event: 'event 2',
action: (data) => {
console.log('received event 2', data);
}
});

// when we have the subscriptions, we will fire up 2 events to event 1, using a delay via setTimeout()
setTimeout(() => {
my_observer.publish('event 1', 'Sending data to event 1 listeners/subscribers');
}, 1500);

// and lets fire up one event to the event 1 subscriber
setTimeout(() => {
my_observer.publish('event 1', 'Sending data to event 2 listeners');
}, 2500);

We should be able to see the 3 events received and being responded to.
Please test the code to see the observers working in action.

Congratulations!

Resources:

JavaScript for beginners - learn by doing

Saturday, November 23, 2019

Store state management with RXJS BehaviorSubject

Basically, we will be creating and using an observer with the help of RXJS library. If you would like to see more of the RXJS in action you can take a look at this Angular course.


It is important to notice that the store will keep the immutable state principle. Which means that all the operations we are about to do on the stored objects inside will not modify the store itself, but will instead produce and return a new store.
Using immutability brings benefits such as preventing data race conditions as well as working with a different from the latest state of the same object when several observers are interacting with it.

For the demonstration, we will create 2 HTML buttons, which when triggered will increment or decrement the values of #inc element data
<button id="inc">+</button> <button id="dec">-</button>

<span id="state"></span>

#state will respond to changes inside of our observed data.

Then, to use behavioursubject we will import it from the rxjs library. It has the ability to save the last emitted value inside itself (thus imitating state) and when a subscriber asks for the saved data (subscribes to the behavior subject) the behavior subject will emit it.

import { BehaviorSubject } from "rxjs";

// we create a class Store with initial state object which has data inside( name and votes). All this initialization is done inside the constructor of the class.
class Store {
  constructor() {
    this.initialState = {
      data: [
        {
          name: "initial name",
          votes: 0
        }
      ]
    };

// we create a new behavior subject and load up the initialState data inside.
    this.subject$ = new BehaviorSubject(this.initialState);

// we create an observable from the subject, in order to be able to access the data from the behavior subject as read-only (i.e not to be able to write and populate values to other subscribers, thus creating chaos inside the data logic).
    this.state$ = this.subject$.asObservable();
  }

// We then use two functions (getters and setters, which get and set the state inside the behavior subject. (with .next() we emit values to the subject, .getValue() is not used very often, and here is just to get the current value inside the subject)
)

  get state() {
    return this.subject$.getValue();
  }
  setState(nextState) { // the function can be also defined as: set state()
    this.subject$.next(nextState);
  }
}

// now it is time to create an object from our previously defined class Store();
const store = new Store();

// When we click on the + button we set a new state inside of our store.

// Note that we have data and state, so each state is characterized with its own data. Here we just modify the initial(old) state with our data object {name, votes}.
document.querySelector("#inc").onclick = function() {
  store.setState(
    //add new objects
    {
      ...store.state,
      data: [...store.state.data, { name: "initial name", votes: 0 }]
    }
  );
};

Searching/querying objects inside the store. In our store, we can also have objects with different names. Here is how to do it: we start by attaching an event handler to the click on the + button:

document.querySelector("#inc").onclick = function() {
// this time we can search for a particular named object, we are interested in inside the store
let searched_name = "initial name";
  store.setState(
    {
      ...store.state,
      data : store.state.data.map(store_data => { // we loop through the whole store
        // console.log('data inside the store: '+JSON.stringify(inner_data));
        if (store_data.name === searched_name) {

// and if we have a match we change the corresponding store object by incrementing its current votes property with 1

          return {...store_data, votes: store_data.votes + 1 };
        }
        return store_data; // after the whole mapping logic, we return the modified 'stored_data' variable to be a property of the 'data' variable.
      })
    }
  );

Note: inside setState we return not a modified old store.set object, but a newly created object so keeping the immutability rule true.

// Here is how to delete objects based on searched_name variable. We loop throughout all the store data, compare and return only the objects which are not equal to the deleted value. Once again keep an eye on the immutability principle, that we are returning a completely new object and don't modify the store.state object by deleting its entries.
document.querySelector("#dec").onclick = function() {

 store.setState(
{
      ...store.state,
      data : store.state.data.filter(store_data => {
    return    store_data.name !== searched_name
      })
    }
  );
};
});

Last but not least if we want our HTML to reflect on the changes from the store we subscribe to BehaviorSubject:
store.state$.subscribe(state => {
// we can either display the current state to the console
  console.log("current state: " + JSON.stringify(state));
// or just place it inside our #state span element
document.querySelector('#state').innerHTML = state.votes;
});

...

Here is an alternative version, which achieves a similar functionality:

//initially we set out empty state object {}:
let initialState = {};

class AppState {
// we again create our behavior subject to store the empty initial state
  private stateSubject = new BehaviorSubject(initialState);
//then we create an observable out of the subject
  state$ = this.stateSubject.asObservable().pipe(
    scan((acc, newVal) => { // on every new value that the subject receives, via the scan function, we get it, together with its previous value acc (scan() in rxjs() behaves like reduce in javascript)
      return { ...acc, ...newVal }; // we create a new object consisting of the old accumulator value plus the newValue changes applied
    })
  );

// here is the important dispatch function, which simply via .next() places a new payload into a specific object key, thus ensuring that the particular object will have its new state set.
  dispatch(obj) {
    this.stateSubject.next(
      { [obj.key]: obj.payload }
    );
  }

}

// optionally we can debug the state inside the observable using Angular's async and json pipes:
{{ appState.state$ | async | json }}

// Here is how we can use the dispatch method: we just send new data to a specific key of our state.
this.appState.dispatch({
      key: 'person',
      payload: {
        name: '',
        website: ''
      }
    });

Congratulations and enjoy the Angular course!

Subscribe To My Channel for updates