Angular 2 — Implementing FLUX architecture

A simple and elegant way to implement unidirectional data flow

Flux is an architecture for unidirectional data flow. By forcing the data to flow in a single direction, Flux makes it easy to reason “how data-changes will affect the application” depending on what actions have been issued. The components themselves may only update application-wide data by executing an action to avoid double maintenance nightmares.

Redux — usually seen with React but it can be used separately — proved to be a successful implementation of Flux. It can handle application state and bind it to the User Interface in a very effective way. RefluxJS, another independent implementation, refactored Flux to be a bit more dynamic and more Functional Reactive Programming (FRP) friendly.

Inspired by these implementations, I wrote a library StateX, that will help you implement a unidirectional data flow architecture for angular 2 (or above). In this article, we are going to see how to use this library to implement Flux architecture in 5 simple steps.

Install

npm install statex --save

5 Simple Steps

1. Define State

To get the best out of TypeScript, declare an interface that defines the structure of the application-state. Just like in Redux, application-state (or simply State ) is an object that will contain the entire data of your application. This step is optional; you could simply get away with any , but I strongly recommend that you define an application state.

If you’ve used different flux alternatives in the past — with the state of your application spread across multiple stores — you may think the way Redux manages your app in just one object would bring about performance issues, especially as the object becomes increasingly large. The short answer is NO. Read this for details.

2. Define Action

In Redux, stores need to have big switch statements to do static type checks of actions using string comparisons. Your application, as it grows, would become more and more difficult to maintain, due to the simple fact that a small typo could bring trouble.

So here, actions are defined as classes with the necessary arguments passed on to the constructor. This way we will benefit from the type checking; never again we will miss-spell an action, miss a required parameter or pass a wrong parameter.

Remember to extend the action from Action class that statex provides. This makes your action listenable and dispatch-able.

3. Create Store & Bind Action

Simplicity is the key feature of this library. It uses the power of Decorators to bind a reducer function with an Action.

The second parameter to the reducer function ( addTodo ) is an action (of type AddTodoAction ); action uses this information to bind the correct action. Also remember to extend this class from Store .

Did you notice @Injectable() ? Well, stores are injectable modules and uses Angular's dependency injection to instantiate. So take care of adding store to the providers list and to inject it into app.component . Read Organizing Stores section to understand more.

4. Dispatch Action

No singleton dispatcher! Instead this library lets every action act as dispatcher by itself. One less dependency to define, inject and maintain.

Every action that statex defines are dispatch-able and listenable.

5. Consume Data

Consuming data is the crucial part. We don’t want UI to process a data if it hasn’t changed. @data decorator uses a selector function to select a subset of the application state to work with. The property gets updated only when the value, returned by the selector function, changes from previous state to the current state. Additionally, just like a map function, you could map the data to another value as you choose. It is compatible with TypeScript’s type checking.

Use @data decorator and a selector function (parameter to the decorator) to get updates from application state.

In this case todos gets updated when state.todos change. Life is not so simple always! We may need to derive additional properties from the data, sometimes using complex calculations.

Therefore @data can be used with functions as well.

Immutable Application State

To take advantage of Angular 2’s change detection strategy — OnPush — we need to ensure that the state is indeed immutable.

Immutable collections should be treated as values rather than objects. While objects represents some thing which could change over time, a value represents the state of that thing at a particular instance of time. — Immutable.js

This library uses seamless-immutable — an immutable JS data structures which are backwards-compatible with normal Arrays and Objects — to keep application state immutable (comparison between seamless-immutable and Facebook’s famous Immutable.js library is here).

Since application state is immutable, the reducer functions will not be able to update state ; any attempt to update the state will result in error.

Therefore a reducer function should either return a portion of the state that needs change (recommended) or a new application state wrapped in ReplaceableState , instead.

Organizing Stores

Create STORES array and a class Stores (again @Injectable ) to maintain stores. When you create a new store remember to:

Add the store to the STORES array Inject to the Store 's constructor

As you setup the project, add STORES to the providers array in app.module.ts

And finally, inject Stores into your root component ( app.component.ts )

Making Your Code AOT Compatible

If you have used a version prior to v1.0.0 , this section will help you refactor your code to make it AOT compatible. The selector function to @data decorator must be an exported standalone function, to avoid the below AOT error:

ERROR in Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function

Therefore refactor your code from:

@Component({

...

})

export class TodoComponent {



@data((state: State) => state.todos)

todos: Todo[]; }

to

export function selectTodos(state: State) {

return state.todos;

} @Component({

...

}

export class TodoComponent extends DataObserver {



@data(selectTodos)

todos: Todo[]; }

Remember to extend your class from DataObserver . It is essential to instruct Angular Compiler to keep ngOnInit and ngOnDestroy life cycle events, which can only be achieved by implementing OnInit and OnDestroy interfaces. Because of this constraint all components using @data must extend itself from DataObserver which sets ngOnInit and ngOnDestroy properly; @data in-turn depends on these functions. However if you would like to extend your class from your-own base class you may do so after making sure ngOnInit and ngOnDestroy are implemented properly.

Sample Code

Sample code is here:

This library is also available for React. Please check

Hope this article was helpful to you. Please make sure to checkout my other projects and articles. Enjoy coding!