在同个angular应用的控制器之间进行通信可以有很多种不同的方式,本文主要讲两种:
基于scope继承的方式和基于event传播的方式
基于scope继承的方式
最简单的让控制器之间进行通信的方法是通过scope的继承。假设有两个控制器Parent、Child,Child 在 Parent 内,那Child 可以称为子控制器,它将继承父控制器Parent的scope。这样,Child就可以访问到Parent的scope中的所有函数和变量了。
需要注意的是,由于scope的继承也是基于Js的原型继承,如果变量是基本类型的,那在Child中的修改(写),有可能会导致Parent中的数据变脏。
基本类型变量的继承
1 function Sandcrawler($scope) { 2 $scope.location = 'Mos Eisley North'; 3 $scope.move = function(newLocation) { 4 $scope.location = newLocation; 5 } 6 } 7 function Droid($scope) { 8 $scope.sell = function(newLocation) { 9 $scope.location = newLocation; 10 } 11 } 12 // html 13 <div ng-controller="Sandcrawler"> 14 <p>Location: </p> 15 <button ng-click="move('Mos Eisley South')">Move</button> 16 <div ng-controller="Droid"> 17 <p>Location: </p> 18 <button ng-click="sell('Owen Farm')">Sell</button> 19 </div> 20 </div>
看完上面的代码我们知道,location
属性是直接被注册到 $scope 中的,Droid控制器所拥有的scope从Sandcrawler控制器的scope中继承了这个属性并且可以读取它。看以下两个假设场景:
-
如果 Sandcrawler 中改变了
location
属性,在 Droid 中也会读取到这个改变;在 view 中的表现则是:点击了Move
按钮的话,两个 p 标签都会显示Mos Eisley South
-
反过来,如果 Droid 中对
$scope.location
进行改写,它只改写自己scope中location
属性的值,它不会影响 Sandcrawler 中的这个属性的值;在 view 中的表现则是:当点击了Sell
按钮之后,两个控制器scope之间的数据共享就不复存在了,之后无论点多少次Move
按钮,都影响不了 Droid 中的 p 标签的显示了
经过上面的教训,有时候我们想要达到的效果可能达不到(如点了 Sell
按钮之后再点 Move
还想让它起作用),这样在ng的开发者中逐渐达成了一个一致的约定,千万不要把那些可以被子级scope改写的属性用基础类型直接添加在 $scope 对象上,而是应该尽可能地用对象类型去添加。
对象类型变量的继承
通过上面的结论我们知道,可以用对象类型的变量来作为属性添加到 $scope 中去,这样,只要是引用了这个对象的,无论是谁,在哪个控制器里面,对这个对象变量的改写都会影响都所有引用了这个对象的实例。看下面的代码:
1 function Sandcrawler($scope) { 2 $scope.sandcrawler.location = 'Mos Eisley North'; 3 } 4 function Droid($scope) { 5 $scope.summon = function(newLocation) { 6 $scope.sandcrawler.location = newLocation; 7 } 8 } 9 // html 10 <div ng-controller="Sandcrawler"> 11 <p>Sandcrawler Location: </p> 12 <div ng-controller="Droid"> 13 <button ng-click="summon('Owen Farm')"> 14 Summon Sandcrawler 15 </button> 16 </div> 17 </div>
跑一下上面的代码就知道,当我们使用“召唤术”的时候,可以改写 Sandcrawler 控制下的 p 标签的显示了。
基于event传播的方式
基于scope继承的方式只能处理父子级控制器之间的通信问题,不能处理兄弟/相邻控制器之间的通信问题。这时候,我们需要使用基于event传播的方式来进行通信,这里,ng为我们提供了三个方法:$on
, $emit
,$broadcast
,需要明确的是:这种方法不仅可以处理兄弟scope间的通信问题,对于解决父子scope间的通信也是毫无压力。
子-->父:$emit
整个过程是这样的:
-
子scope中的控制器通过
$scope.$emit
触发一个事件向上传播 -
这个事件会经过每一层的父scope,至于处不处理是父scope自己的事情了
-
如果处理,就在想要处理的那个祖先scope中放一个
$scope.$on
监听着就行了三四三 -
1 // 父scope上的控制器 2 function Sandcrawler($scope) { 3 $scope.location = 'Mos Eisley North'; 4 $scope.$on('summon', function(e, newLocation) { 5 $scope.location = newLocation; 6 }); 7 } 8 // 子scope上的控制器 9 function Droid($scope) { 10 $scope.location = 'Owen Farm'; 11 $scope.summon = function() { 12 $scope.$emit('summon', $scope.location); 13 } 14 } 15 // html 16 <div ng-controller="Sandcrawler"> 17 <p>Sandcrawler Location: </p> 18 <div ng-controller="Droid"> 19 <p>Droid Location: </p> 20 <button ng-click="summon()">Summon Sandcrawler</button> 21 </div> 22 </div>
如果你不想让你的事件再往更上层传播,在
$on
中的处理函数调用e.stopPropagation()
即可。
父-->子:$broadcast
从父到子,用另外一个方法就是了,同样用 $on
监听着,all done,看下面代码:
// 父scope上的控制器 function Sandcrawler($scope) { $scope.location = 'Mos Eisley North'; $scope.recall = function() { $scope.$broadcast('recall', $scope.location); } } // 子scope上的控制器 function Droid($scope) { $scope.location = 'Owen Farm'; $scope.$on('recall', function(e, newLocation) { $scope.location = newLocation; }); } // html <div ng-controller="Sandcrawler"> <p>Sandcrawler Location: </p> <button ng-click="recall()">Recall Droids</button> <div ng-controller="Droid"> <p>Droid Location: </p> </div> </div>
同级之间
拥有同个父scope的子级scope之间,也就是兄弟/相邻scope之间的通信,其实是借助“奶爸”传递消息的:
-
子级scope中有谁想传消息了,
$emit
一个给“奶爸” -
然后通过“奶爸”
$broadcast
给所有孩子这个相同的信息,当然发出信息的那个可以选择是否要忽略掉自己发出的信息
1 // 父scope上的控制器 2 function Sandcrawler($scope) { 3 $scope.$on('requestDroidRecall', function(e) { 4 $scope.$broadcast('executeDroidRecall'); 5 }); 6 } 7 // 子scope上的控制器 8 function Droid($scope) { 9 $scope.location = 'Owen Farm'; 10 $scope.recallAllDroids = function() { 11 $scope.$emit('requestDroidRecall'); 12 } 13 $scope.$on('executeDroidRecall', function() { 14 $scope.location = 'Sandcrawler'; 15 }); 16 } 17 // html 18 <div ng-controller="Sandcrawler"> 19 <div ng-controller="Droid"> 20 <h2>R2-D2</h2> 21 <p>Droid Location: </p> 22 <button ng-click="recallAddDroids()">Recall All Droids</button> 23 </div> 24 <div ng-controller="Droid"> 25 <h2>C-3PO</h2> 26 <p>Droid Location: </p> 27 <button ng-click="recallAddDroids()">Recall All Droids</button> 28 </div> 29 </div>
上面代码中要注意的是:子scope通过 $emit
发出的事件名不能与父scope用 $broadcast
的事件名一样,如果有传参数,那当然参数可以一样,因为参数就是我们要传的数据。事件名不能一样是为了防止进入死循环。