BUILDING ANGULAR APPS USING FLUX ARCHITECTURE
Flux is an architectural pattern based on unidirectional data flow. Although it is originated in the React community, it is general and can be used with any non-opinionated framework. In this article I will show how to use it with Angular.
WHAT IS FLUX
There are four core components in Flux.

- Views render data and trigger actions.
- The dispatcher propagates actions from views to stores.
- Stores contain application logic and state.
A typical Flux interaction looks as follows:
- The view triggers an action.
- The action layer, using the data provided by the view, constructrs an action object.
- The dispatcher receives the action, puts it in its action queue, and later notifies the store about it.
- The store updates its state based on the action and broadcasts a change event.
- The view reads data from the store and updates itself.
REPLACEMENT FOR MVC?
MVC is a very old pattern. And as a result, it is hardly possible to find two people who would agree on what it really means. I see MVC as a component pattern, with the view and the controller forming the component. Defined like this, MVC does not conflict with Flux and can be used in the view layer.
APPLICATION
I won’t cover the pattern itself in great detail. So if you feel confused after reading the post, I highly recommend you to check outthis screencast series by Joe Maddalone, which is absolutely excellent. In the series Joe Maddalone builds a shopping cart application, a simplified version of which I am going to build in this post.

The application displays two tables: the catalog and the shopping cart, and supports two operations: addItem and removeItem.
ANGULAR & FLUX
VIEWS
Let’s start with the catalog table.
<table ng-controller="CatalogCtrl as catalog">
<tr ng-repeat="item in catalog.catalogItems">
<td>{{item.title}}</td>
<td>{{item.cost}}</td>
<td>
<button ng-click="catalog.addToCart(item)">Add to Cart</button>
</td>
</tr>
</table>
Where CatalogCtrl is defined as follows:
var m = angular.module('cart', []);
class CatalogCtrl {
constructor(catalogItems, cartActions) {
this.cartActions = cartActions;
this.catalogItems = catalogItems;
}
addToCart(catalogItem) {
this.cartActions.addItem(catalogItem);
}
}
m.controller("CatalogCtrl", CatalogCtrl);
To make it simple, let’s hardcode catalogItems.
m.value("catalogItems", [
{id: 1, title: 'Item #1', cost: 1},
{id: 2, title: 'Item #2', cost: 2},
{id: 3, title: 'Item #3', cost: 3}
]);
The controller does not do much. It has no application logic: it just triggers the addItem action. It is worth noting that not everything should be handled as an action and go through the pipeline. Local view concerns (e.g., UI state) should be kept in the view layer.
ACTIONS
The action layer is nothing but a collection of helper functions, which, at least in theory, should have a helper for every action your application can perform.
var ADD_ITEM = "ADD_ITEM";
m.factory("cartActions", function (dispatcher) {
return {
addItem(item) {
dispatcher.emit({
actionType: ADD_ITEM,
item: item
})
}
};
});
DISPATCHER
The dispatcher is a message-bus service that propagates events from views to stores. It has no application logic. A real project should probably use the Dispatcher library from Facebook, or implement it using RxJS. In this article I will use this simpleEventEmitter class, just to illustrate the interaction.
class EventEmitter {
constructor() {
this.listeners = [];
}
emit(event) {
this.listeners.forEach((listener) => {
listener(event);
});
}
addListener(listener) {
this.listeners.push(listener);
return this.listeners.length - 1;
}
}
m.service("dispatcher", EventEmitter);
Since all actions go through the dispatcher, it is a great place to add such things as logging.
STORE
A store contains application logic and state for a particular domain. It registers itself with the dispatcher to be notified about the events flowing through the system. The store then updates itself based on those events. And what is really important, there is no other way to update it. Views and other stores can read data from the store, but not update it directly. The store is observable, so it emits events that views can listen for.
This’s one way to implement the cart store.
class CartStore extends EventEmitter {
constructor() {
super();
this.cartItems = [];
}
addItem(catalogItem) {
var items = this.cartItems.filter((i) => i.catalogItem == catalogItem);
if (items.length == 0) {
this.cartItems.push({qty: 1, catalogItem: catalogItem});
} else {
items[0].qty += 1;
}
}
removeItem(cartItem) {
var index = this.cartItems.indexOf(cartItem);
this.cartItems.splice(index, 1);
}
emitChange() {
this.emit("change");
}
}
Next, we need to register it.
m.factory("cartStore", function (dispatcher) {
var cartStore = new CartStore();
dispatcher.addListener(function (action) {
switch(action.actionType){
case ADD_ITEM:
cartStore.addItem(action.item);
cartStore.emitChange();
break;
case REMOVE_ITEM:
cartStore.removeItem(action.item);
cartStore.emitChange();
break;
}
});
//expose only the public interface
return {
addListener: (l) => cartStore.addListener(l),
cartItems: () => cartStore.cartItems
};
});
Since stores is where all of the application logic is contained, they can become very complicated very quickly. In this exampleCartStore is implemented using the transaction script pattern, which works fairly well for small applications, but breaks apart when dealing with complex domains. In addition, stores are responsible for both the domain and application logic. This, once again, works well for small applications, but not so much for larger one. So you may consider separating the two.
CLOSING THE LOOP
We are almost done. There is only one piece left to see the whole interaction working: we need to implement the cart table.
<h1>Cart</h1>
<table ng-controller="CartCtrl as cart">
<tr ng-repeat="item in cart.items track by $id(item)">
<td>{{item.catalogItem.title}}</td>
<td>{{item.qty}}</td>
<td>
<button ng-click="cart.removeItem(item)">x</button>
</td>
</tr>
</table>
class CartCtrl {
constructor(cartStore, cartActions) {
this.cartStore = cartStore;;
this.cartActions = cartActions;
this.resetItems();
cartStore.addListener(() => this.resetItems());
}
resetItems() {
this.items = this.cartStore.cartItems();
}
removeItem(item) {
//to be implemented
}
}
m.controller("CartCtrl", CartCtrl);
The controller listens to the store, and when that changes, resets the list of cart items.
INTERACTION

SUMMING UP
Flux is an architectural pattern based on unidirectional data flow. It can be used with any non-opinionated framework. In this blog post I have shown how it can be used with Angular.
READ MORE
- The example from this post, including the remove item functionality.
- Flux Architecture Overview
- Flux Arhitecture by Joe Maddalone
- Read up on CQRS. CQRS is a well-established pattern for building server-side applications. Flux is built around the same set of ideas. There is a lot of great material on CQRS: pros and cons, how to structure actions, and so on. Most of it can be applied to Flux.
BUILDING ANGULAR APPS USING FLUX ARCHITECTURE的更多相关文章
- Building Web Apps with SignalR, Part 1
Building Web Apps with SignalR, Part 1 In the first installment of app-building with SignalR, learn ...
- Building Android Apps 30条建议
Building Android Apps — 30 things that experience made me learn the hard way There are two kinds of ...
- Building the Unstructured Data Warehouse: Architecture, Analysis, and Design
Building the Unstructured Data Warehouse: Architecture, Analysis, and Design earn essential techniqu ...
- Flux architecture
[Flux architecture] Flux is a pattern for managing data flow in your application. The most important ...
- [AngularJS] Sane, scalable Angular apps are tricky, but not impossible.
Read fromhttps://medium.com/@bluepnume/sane-scalable-angular-apps-are-tricky-but-not-impossible-less ...
- HoloLens开发手记 - 构建2D应用 Building 2D apps
HoloLens可以让我们在真实世界中看到全息图像内容.但是它本质上还是一台Windows 10设备,这意味着HoloLens可以以2D应用形式运行Windows Store里的大部分UWP应用. 目 ...
- [Angular] Debug Angular apps in production without revealing source maps
Source: https://blog.angularindepth.com/debug-angular-apps-in-production-without-revealing-source-ma ...
- AngularJS + RequireJS
http://www.startersquad.com/blog/AngularJS-requirejs/ While delivering software projects for startup ...
- Kivy: Building GUI and Mobile apps with Python
Intro Python library for building gui apps (think qt, gdk,processing) build from ground up for lates ...
随机推荐
- 白帽子讲web安全——白帽子兵法(设计安全方案中的技巧)
1.Secure By Default原则 白名单:筛选出被允许的,屏蔽其他. 黑名单:屏蔽可能造成的威胁. 2.XSS和SSH XSS攻击:跨站脚本(cross site script)攻击是指恶意 ...
- 一个Android开发妹子的找工作心酸史
阿里:实习二面被KO 1,人生接到的第一个电话面试来自大家都说好的阿里,心情激动,说话颤抖,本以为没有戏,然而第二天接到了二面的电话有点小激动.然后就是被ko了,死的原因很简单,那时候单纯的自己什么都 ...
- TCP/IP详解与OSI七层模型
TCP/IP协议 包含了一系列构成互联网基础的网络协议,是Internet的核心协议.基于TCP/IP的参考模型将协议分成四个层次,它们分别是链路层.网络层.传输层和应用层.下图表示TCP/IP模型与 ...
- FastAdmin 开发时如何与官方同步升级
FastAdmin 开发时如何与官方同步升级 使用 FastAdmin 开发时为了与官方同步升级,推荐使用 git 管理代码. 官网上提供的完整包是为了方便第一次使用的人快速测试. 我一般是给官方的 ...
- bzoj 3730 震波——动态点分治+树状数组
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3730 查询一个点可以转化为查询点分树上自己到根的路径上每个点对应范围答案.可用树状数组 f ...
- 解决 eclipse tomcat cannot create a server using the selected type
解决的方法 1.退出eclipse: 2.打开 [工程目录下]/.metadata/.plugins/org.eclipse.core.runtime/.settings目录: 3.删除org.ecl ...
- Oracle.DataAccess.dll 部署安装
Oracle.DataAccess.dll 要拷贝到项目发布目录 项目发布的时候,还必须要拷贝以下几个文件在运行目录1.oci.dll 2.oraociicus11.dll 3.OraOps11w.d ...
- T-SQL 总结
SP0_AddLinkedServer.sql [创建Linked SQL Server ] USE [master] GO ) drop procedure dbo.SP_Temp_AddLinke ...
- python写exploit采集器
前言: 根据天文地理与风水学,我掐指一算的看到了一篇不错的文章,文章里面写到整理exploit 我顿时心理想写一个exploit采集器,那么说时迟那时快.就开始写了 代码: 思路:http://exp ...
- Ansible Jinja if 生成不同的MySQL文件