在同個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 的事件名一樣,如果有傳參數,那當然參數可以一樣,因為參數就是我們要傳的數據。事件名不能一樣是為了防止進入死循環。
