Javascript Design Patterns Flux

javascript-design-patterns

// JavaScript - Design Patterns - Flux:

Flux is a architectural design pattern introduced and used by Facebook as an 
alternative to the traditional MVC patterns to build modern web applications.  
Flux is not a library and implementing it within our application can be done 
without depending on any 3rd party code.  Moreover, Flux is language and 
framework-agnostic and can be used outside single page application and 
JavaScript context.  The core ideas behind Flux are inspired by game programming 
specifically rendering of the changes of our domain model.

The core idea behind Flux pattern is unidirectional data flow implemented using 
the for major "machines" in the Flux "pipeline" (Actions, Dispatcher, Stores, 
and the Views).

The flow of data through the Flux pipeline:

Action -> Dispatcher -> Store -> View -> Action ...

When the user interact with the View (i.e. a component), the view propagates an 
Action through a Dispatcher (i.e. a message bus) to the Stores where your domain 
model or state and the application logic are stored.  Once the Store updates its 
domain model, it propagates a "change" event to which components in our 
application can subscribe, and re-renter themselves accordingly to the new data.

The Dispatcher acts as a central message bus responsible for distributing the 
incoming Actions to the appropriate stores.  It has no logic of its own and 
simply acts as a hub for Action creator methods to push Actions to.  Stores will 
listen on the dispatcher for incoming Actions responding to relevant onces.

Stores contain and manage our application's model and logic pertaining to 
particular domain of our application.  An application can have many stores, 
each focus on a specific aspect of our domain.  Stores are subscribed to the 
Dispatcher, our event bus, and listen to Actions that are relevant to them.  
The important part is that nothing outside the store can modify the application 
state stored within it directly.  Nothing outside the Store should know how the 
store manage its state, and the Store can be accessed in read-only fashion (i.e. 
by providing getters to the outside world).

The only way for a Store to modify the domain model of our application is by 
responding to an action coming through a Dispatcher.

Actions are simple objects representing actions.  They usually contain an 
action name and an optional payload.  Most of the time, actions are emitted from 
the views.

Rx.Subject is a combination of an Observer and Observable in one class.  It is 
used here for convenience.

angular.module('ngcourse.dispatcher', [])
  .service('dispatcher', Rx.Subject);

In the above code, we create a module named 'ngcourse.dispatcher' with no 
dependencies, and then we create a service named 'dispatcher'.

app/src/actions/task-actions.ts:

import {TASK_ACTIONS} from '../action-constants';

export class TaskActions {
  static $inject = ['dispatcher', 'tasksService'];
  constructor( 
    private dispatcher: Rx.Subject<any>,
    private tasksService: TasksService
  ) { }

  getTasks() {
    this.tasksService.getTasks()
      .then(tasks => this.dispatcher.onNext({
        actionType: TASK_ACTIONS.GET_TASKS_RESPONSE,
        tasks: tasks
      }))
      .catch(error => this.dispatcher.onNext({
        actionType: TASK_ACTIONS.GET_TASKS_RESPONSE_ERROR,
        error: error
      })); 
  }
}

app/src/actions/action-constants.ts:

export const TASK_ACTIONS = {
  GET_TASKS_RESPONSE: 'GET_TASKS_RESPONSE',
  GET_TASKS_RESPONSE_ERROR: 'GET_TASKS_RESPONSE_ERROR'
};

It is a poor practice to use hard-coded strings.  Therefore, we put all those 
strings into a "constants" file.  We can see that the string on the left and 
on the right are exactly the same, and we might not see the benefit of having 
this "constants" file, but having all these constants in one place may allow us 
to look them up quickly, or may allow us to do other things.

The above code show us how to implement the Dispatcher and Actions class.  For 
the Dispatcher part, we use Rx.Subject.  Our 'Actions' class has a getTasks 
method, which use the dispatcher to emit an action (push an action onto the 
dispatcher).  Our 'Action' instance contains two attributes (actionType, and 
tasks).  The tasks attribute is the optional payload.

app/src/store/task/tasks-store.ts:

import {TASK_ACTIONS} from '../actions/action-constants';
export class TasksStore {
  private _tasks;
  static $inject = ['dispatcher'];
  constructor(private dispatcher) { 
    this.registerActionHandlers();
  }

  private registerActionHandlers() {
    this.dispatcher.filter(
      action => action.actionType === TASK_ACTIONS.GET_TASKS_RESPONSE
    ).subscribe(
      (tasks) => {
        // Handle the action here
      }
    );
  }

  private getTasks() {
  }

};

Our Store is responsible for providing the list of tasks that we will receive 
from the server.  The Dispatcher is available to us from the constructor 
injection.  We need to listen for incoming Actions relevant to our store.

Notice that in our TaskActions class, we have the getTasks method which uses 
TasksService to fetch the tasks.  And based on whether the request is resolved 
or rejected, we dispatch the appropriate action.  If the TaskActions class has 
the data (the tasks), then what is the purpose of the store?  Is the purpose of 
the store is to manage the data model?

We also added a call to getTasks() method from within the run block for the app 
module:

angular.module('ngcourse', [...])
  ...
  .run((tasksAction) => {
    tasksAction.getTasks();
  });

The question is how does Angular know that it needs to create an object of the 
TasksAction class and pass it onto this anonymous function that we passed to 
the run method?  Does Angular use the name of the variable to determine the name 
of the class?

So far, we have managed to get the data we need from the server and make it 
available to the store (in the above code, when we push the action onto the 
dispatcher, we also provide the payload, so we made the data available to the 
dispatcher, and subsequently to the the store).  The next step is to publish 
this data to our components (store subscribers).  The components will also be 
listening to the changes to the store.  We update our TasksStore:

export class TasksStore {
  private _tasks Rx.ReplaySubject<any[]>;
  private _error: Rx.ReplaySubject<any>;
  static $inject = ['dispatcher'];
  constructor(private dispatcher: Rx.Subject<any>) {
    this._tasks = new Rx.ReplaySubject<any[]>(1);
    this._error = new Rx.ReplaySubject(1);
  }
  get tasks() {
    return this._tasks;
  }

  get error() {
    return this._error;
  }
}

ReplaySubject is a special subject that will replay a value from its buffer when 
a new subscriber is added.

We have added getters for both _tasks and _error.  These can be used by an 
observer to subscribe to and be notified whenever a change occurs to our store.

The last step is to notify our observer of changes within our store.  For this, 
we modify the action handlers as follows:

private registerActionHandlers() {
  this.dispatcher.filter(
    action => action.actionType === TASK_ACTIONS.GET_TASKS_RESPONSE
  ).subscribe(
    action => this._tasks.onNext(action.tasks)
  );

  this.dispatcher.filter(
    action => action.actionType === TASK_ACTIONS.GET_TASKS_RESPONSE_ERROR
  ).subscribe(
    action => this._error.onNext({
      type: action.actionType,
      error: action.error
    })
  );
}

To use stores within components:

export class TaskListComponent {
  tasks: Task[];
  users: {};
  user;
  errorMessage: string;
  ...
  constructor(private tasksStore: TasksStore) {
    this.tasks = [];
    tasksStore.tasks.subscribe(tasks => this.tasks = tasks);
  }
}

In the above code, an instance of TasksStore is injected into the constructor.  
When a change occurs within our store, our component get notified and updates 
its own (view related) list of tasks.  If an error has occurred within the 
store, the component get notified as well and can display it to the user, in 
some friendly form.

Since we used a ReplaySubject with a buffer of 1, we are guaranteed to get 
notified of the data within the store.  Even if our component got instantiated 
after this event has already occurred.

Note that we did not have to change our TaskComponent implementation.  This is 
because the TaskListComponent is passing its data down ot it.  It is recommended 
that only top level components subscribe to stores, then pass that data down to 
other components via the normal mechanism (without using observables).

It is important that Stores provide read-only access to its data.  Stores should 
not provide setters to change their state.  In our app so far, we didn't 
maintain states in the store.  We fetch data from the server and publish it as 
is.  In a real world scenario, we might actually want to maintain some data in 
the store, and then reference to that state is pushed to the observers.

But what if a component want to make a change to the data retrieved from a 
store.  If that were to happen, it would change the state of the store.  This is 
why our stores should maintain their state in immutable data structure.

https://facebook.github.io/immutable-js/

To achieve this, our store can also make a copy of the data and publish the copy 
of the data to the other components instead of publishing its raw data.  For 
example, using the formJS method provided by the Immutable.js library, we added 
to TasksStore:

private _data;

and modify the registerActionHandlers method:

private registerActionHandlers() {
  this.dispatcher.filter(
    action => action.actionType === TASK_ACTIONS.GET_TASKS_RESPONSE
  ).subscribe(
    action => {
      this._data = fromJS(tasks);
      this._tasks.onNext(_data)
    }
  );
}

So far, we responded to changes within our Stores.  Let's make a view that 
emits an Action.

Create a new file ( app/src/components/task-add/task-add-component.ts ):

export class TaskAddComponent {
  static selector = 'ngcTaskAdd';
  static directiveFactory: ng.IDirectiveFactory = () => {
    return {
      restrict: 'E',
      scope: {},
      controllerAs: 'ctrl',
      bindToController: {},
      controller: TaskAddComponent,
      template: require('./task-add-component.html')
    };
  };

  static $inject = ['$log', 'tasksActions'];
  constructor(
    private $log,
    private tasksActions
  ) {
    //
  }

  save(task) {
    this.tasksAction.addTask(task);
  }
}

and the corresponding task-add-component.html:

<div>
  <div>
    <h4>Add Task</h4>
  </div>
  <form>
    <label>Owner</label>
    <input type="text" ng-model="newTask.owner">
    <label>Description</label>
    <input type="text" ng-model="newTask.description">
    <button ng-click="ctrl.save(newTask)">Save</button>
  </form>
</div>

Define the new action in app/src/action/task-actions.ts:

addTask(newTask) {
  this.taskService.addTask(newTask)
    .then(() => this.getTasks())
    .catch(error => {
      this.dispatcher.onNext({
        actionType: TASK_ACTIONS.GET_TASKS_RESPONSE_ERROR,
        error: error
      });
    });
}

Add the new addTask method to app/src/services/tasks/tasks-service.ts:

addTask(task) {
  returh this.serverService.post('/some/url', task);
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License