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

BUILDING ANGULAR APPS USING FLUX ARCHITECTURE的更多相关文章

  1. Building Web Apps with SignalR, Part 1

    Building Web Apps with SignalR, Part 1 In the first installment of app-building with SignalR, learn ...

  2. Building Android Apps 30条建议

    Building Android Apps — 30 things that experience made me learn the hard way There are two kinds of ...

  3. Building the Unstructured Data Warehouse: Architecture, Analysis, and Design

    Building the Unstructured Data Warehouse: Architecture, Analysis, and Design earn essential techniqu ...

  4. Flux architecture

    [Flux architecture] Flux is a pattern for managing data flow in your application. The most important ...

  5. [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 ...

  6. HoloLens开发手记 - 构建2D应用 Building 2D apps

    HoloLens可以让我们在真实世界中看到全息图像内容.但是它本质上还是一台Windows 10设备,这意味着HoloLens可以以2D应用形式运行Windows Store里的大部分UWP应用. 目 ...

  7. [Angular] Debug Angular apps in production without revealing source maps

    Source: https://blog.angularindepth.com/debug-angular-apps-in-production-without-revealing-source-ma ...

  8. AngularJS + RequireJS

    http://www.startersquad.com/blog/AngularJS-requirejs/ While delivering software projects for startup ...

  9. 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 ...

随机推荐

  1. 【转】让开发变得简单一点- Visual Studio 2010几个让人印象深刻的新功能

    原文网址:http://xhinker.blog.51cto.com/640011/313055/ 引言 "我们的目标,不仅仅是做出几个新功能,而是要回答一个问题:'如何让现在的开发人员生活 ...

  2. 渐进式 jpg 和 交错式 gif png 提高图片站体验

    渐进式 jpg 和 交错式 gif png 提高图片站体验= 渐进式的JPG比原始JPG还要小!! 让图片性感的露给你看~google picasa 和 smashing magazine 都有用到搞 ...

  3. settimeout()在IE8下参数无效问题解决方法

    遇到这个问题,setTimeout(Scroll(),3000); 这种写法在IE8 下 不能够执行,提示参数无效, setTimeout(function(){Scroll()},3000);这种方 ...

  4. 安全关闭MySQL

    想要安全关闭 mysqld 服务进程,建议按照下面的步骤来进行: 0.用具有SUPER.ALL等最高权限的账号连接MySQL,最好是用 unix socket 方式连接: 1.在5.0及以上版本,设置 ...

  5. Android开源项目汇总

    Android很多优秀的开源项目,很多UI组件以及经典的HTTP 访问开源,都能给我们带来一些自己项目的启迪或者引用. 下面简单介绍一下自己收藏的一些项目内容. 项目: Apollo音乐播放器:And ...

  6. OSG和ProLand 的海面仿真

    基于OSG的海面仿真 OSG中国官网 http://www.osgchina.org/ OSG-ocean的效果图如下 proland的效果图如下 下面为OSG和OCEAN的配置 配置方法转自 htt ...

  7. IPv4正则表达式匹配

    IP地址的长度为32位,分为4段,每段8位.用十进制数字表示,每段数字范围为0~255,段与段之间用英文句点“.”隔开.例如:某台计算机IP地址为111.22.33.4. 分析IP地址的组成特点:25 ...

  8. 【转】iOS9适配 之 关于info.plist 第三方登录 添加URL Schemes白名单

    近期苹果公司iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装. 受此影响,当你的应用在 ...

  9. volyaire重振Infiniband

    InfiniBand简 称IB,DoSTOR存储小字典里的解释是,一种新的I/O总线技术,用于取代目前的PCI总线.IB主要应用在企业网络和数据中心,也可以应用在高速线 速路由器.交换机.大型电信设备 ...

  10. 通过 Lua 扩展 NGINX 实现的可伸缩的 Web 平台OpenResty®

    OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库.第三方模块以及大多数的依赖项.用于方便地搭建能够处理超高并发.扩展性极高的动态 W ...