Controller间的数据共享?最佳实践:使用Service

Author Avatar
llp 7月 22, 2016
共计 1,047 字  |   预计阅读 4 分钟  

原文链接 : Sharing Data Between Controllers? Best Practice: Use a Service
原文作者 : DAVE CEDDIA
译者 : llp(web前端领域)
译者注:翻译如有疏漏,欢迎指出!感谢!转载请保留此头部。

Angular开始起来简单易用,甚至说神奇。“哇哦!双向绑定!”

你会迫不及待地去构建你的杰作,直到碰到钉子:你正在像网上每个人建议的那样去构建一个独立的组件,但怎么和其他组件共享数据呢?

或许你在不同的路由里有两个视图都需要访问某些状态变量,又或者你有三个分离的组件需要访问同样的一堆数据。

共享数据的最佳策略是什么呢?用一些变态的控制器继承方案吗?

当然不是,最简单容易的方式就是使用服务(service)。

问题

假设有两个并排的面板,每个面板代表一个指令(directive):

下面是面板1的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
angular.directive('paneOne', function() {
return {
restrict: 'E',
scope: {},
template: [
'<div>',
'<input ng-model="p1.text">',
'<button ng-click="p1.addToList()">Add To List</button>',
'</div>'
].join(''),
controllerAs: 'p1',
controller: function() {
var vm = this;
vm.text = "";
vm.addToList = function() {
// TODO: add to the list in Pane 2 somehow
vm.text = "";
};
}
};
});

面板2的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
angular.directive('paneTwo', function() {
return {
restrict: 'E',
scope: {},
template: [
'<ul>',
'<li ng-repeat="item in p2.listItems">{{ item }}</li>',
'</ul>'
].join(''),
controllerAs: 'p2',
controller: function() {
var vm = this;
// TODO: get this list of items from Pane 1 somehow
vm.listItems = [];
}
};
});

我们想要实现的是在面板1的输入框中输入某些东西,然后点击按钮 “Add To List”, 这条内容就会在面板2的列表中出现。

创建一个服务保持共享状态

为了在两个甚至更多的控制器间共享数据,创建一个服务去扮演中间件的角色。这样可以使控制器(或组件)保持松散耦合:它们不需要知道其他的控制器(或组件)是怎么样的,它们只需要知道数据源 - 你创建的中间件服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
angular.factory('sharedList', function() {
var list = [];
return {
addItem: addItem,
getList: getList
};
function addItem(item) {
list.push(item);
}
function getList() {
return list;
}
});

上面这个服务非常简单,调用 addItem 把数据放到列表里,然后调用 getList 获取整个列表。这真的很简单,甚至不需要去移除或者清空列表项,看到这个方法的简便之处了吧。

在任何需要的地方注入服务

既然我们已经创建好了我们的服务,我们就需要在任何需要访问或修改数据的地方去注入它了。

先从面板1的控制器开始:

1
2
3
4
5
6
7
8
9
10
// Inject sharedList
controller: function(sharedList) {
var vm = this;
vm.text = "";
vm.addToList = function() {
// Stuff the item into the shared list
sharedList.addItem(vm.text);
vm.text = "";
};
}

然后是面板2的控制器,获取数据:

1
2
3
4
5
6
// Inject sharedList
controller: function(sharedList) {
var vm = this;
// Read the data
vm.listItems = sharedList.getList();
}

JSBin里看看运行情况。

但不需要观察器(watchers)吗?

当我写到这儿的时候,我非常确定面板2中的列表在我添加一些观察器前不会自动更新

但是接下来当我把代码放到 JSBin 里的时候…你瞧,它竟然可以更新!为什么呢?

  1. ng-repeat 给循环的数组设置了观察器。
  2. 点击 “Add To List” 触发了一个消化周期 (digest cycle),它就会重新检测 ng-repeat 的观察器。
  3. 因为 sharedData.getList() 返回了一个数组的引用,观察器就会看到 p2.listItems 已经发生了变化。这里是关键:如果 getList 返回一个不同于由 addToList 更改的数组,这就不会触发更新了。

所以:这种通信方式可以在没有观察器的情况下完美运行。但如果你发现失败了的话,检查一下你是怎么传递数据的,或许你需要明确地创建一个观察器检测变化。

概括

  1. 创建一个服务去存放你的数据,并给数据创建 gettersetter 的方法。
  2. 在任何需要这个数据的地方注入这个服务。
  3. 这几乎就是全部内容了(除非你需要观察器 - 这种情况下,你还是添加一个吧)。

感谢阅读!