多個 ng-app 中 Controllers & Services 之間的通訊

來源:互聯網
上載者:User

標籤:ogr   evel   pyc   頁面   boot   nbsp   就會   type   get   

原文發布在個人獨立部落格上,連結:http://pengisgood.github.io/2016/01/31/communication-between-multiple-angular-apps/

通常情況下,在 Angular 的單頁面應用中不同的 Controller 或者 Service 之間通訊是一件非常容易的事情,因為 Angular 已經給我們提供了一些便利的方法:$on$emit$broadcast

在這裡用一個簡單的例子來示範一下這三個方法的用途,完整版代碼也可以參考這裡:

 

style.css

 1 body { 2   background-color: #eee; 3 } 4  5 #child { 6   background-color: red; 7 } 8  9 #grandChild {10   background-color: yellow;11 }12 13 #sibling {14   background-color: pink;15 }16 17 .level {18   border: solid 1px;19   margin: 5px;20   padding: 5px;21 }

index.html

 1 <body ng-app="app" ng-controller="ParentCtrl" class=‘level‘> 2   <h2>Parent</h2> 3   <button ng-click="broadcastMsg()">Broadcast msg</button> 4   <button ng-click="emitMsg()">Emit msg</button> 5   <pre>Message from: {{message}}</pre> 6   <div id=‘child‘ ng-controller="ChildCtrl" class=‘level‘> 7     <h2>Child</h2> 8     <button ng-click="broadcastMsg()">Broadcast msg</button> 9     <button ng-click="emitMsg()">Emit msg</button>10     <pre>Message from: {{message}}</pre>11 12     <div id=‘grandChild‘ ng-controller="GrandChildCtrl" class=‘level‘>13       <h2>Grand child</h2>14 15       <pre>Message from: {{message}}</pre>16     </div>17   </div>18   <div id=‘sibling‘ ng-controller="SiblingCtrl" class=‘level‘>19     <h2>Sibling</h2>20     <button ng-click="broadcastMsg()">Broadcast msg</button>21     <button ng-click="emitMsg()">Emit msg</button>22     <pre>Message from: {{message}}</pre>23   </div>24 </body>

app.js

 1 var app = angular.module(‘app‘, []) 2 app.controller(‘ParentCtrl‘, function($scope) { 3   $scope.message = ‘‘ 4  5   $scope.broadcastMsg = function() { 6     $scope.$broadcast(‘msg_triggered‘,‘parent‘) 7   } 8  9   $scope.emitMsg = function() {10     $scope.$emit(‘msg_triggered‘,‘parent‘)11   }12 13   $scope.$on(‘msg_triggered‘, function(event, from){14     $scope.message = from15   })16 })17 18 app.controller(‘ChildCtrl‘, function($scope) {19   $scope.message = ‘‘20   $scope.broadcastMsg = function() {21     $scope.$broadcast(‘msg_triggered‘,‘child‘)22   }23 24   $scope.emitMsg = function() {25     $scope.$emit(‘msg_triggered‘,‘child‘)26   }27 28   $scope.$on(‘msg_triggered‘, function(event, from){29     $scope.message = from30   })31 })32 33 app.controller(‘GrandChildCtrl‘, function($scope) {34   $scope.message = ‘‘35 36   $scope.$on(‘msg_triggered‘, function(event, from){37     $scope.message = from38   })39 })40 41 app.controller(‘SiblingCtrl‘, function($scope) {42   $scope.message = ‘‘43   $scope.broadcastMsg = function() {44     $scope.$broadcast(‘msg_triggered‘,‘sibling‘)45   }46 47   $scope.emitMsg = function() {48     $scope.$emit(‘msg_triggered‘,‘sibling‘)49   }50 51   $scope.$on(‘msg_triggered‘, function(event, from){52     $scope.message = from53   })54 })

 

在上面的例子中我們可以看出,利用 Angular 已有的一些 API 能夠很方便的在不同 Controller 之間通訊,僅需要廣播事件即可。

上面的代碼之所以能工作,是因為我們一直都有著一個前提,那就是這些 Controller 都在同一個 ng-app 中。那麼,如果在一個頁面中存在多個 ng-app 呢?(儘管並不推薦這樣做,但是在真實的項目中,尤其是在一些遺留項目中,仍然會遇到這種情境。)

先看一個簡單的例子:

style.css

1 .app-container {2   height: 200px;3   background-color: white;4   padding: 10px;5 }6 7 pre {8   font-size: 20px;9 }

index.html 

 1 <body> 2   <div class="app-container" ng-app="app1" id="app1"  ng-controller="ACtrl"> 3     <h1>App1</h1> 4     <pre ng-bind="count"></pre> 5     <button ng-click="increase()">Increase</button> 6   </div> 7   <hr /> 8   <div class="app-container" ng-app="app2" id="app2"  ng-controller="BCtrl"> 9     <h1>App2</h1>10     <pre ng-bind="count"></pre>11     <button ng-click="increase()">Increase</button>12   </div>13 </body>

app.js

 1 angular 2   .module(‘app1‘, []) 3   .controller(‘ACtrl‘, function($scope) { 4     $scope.count = 0; 5  6     $scope.increase = function() { 7       $scope.count += 1; 8     }; 9   });10 11 angular12   .module(‘app2‘, [])13   .controller(‘BCtrl‘, function($scope) {14     $scope.count = 0;15 16     $scope.increase = function() {17       $scope.count += 1;18     };19   });

 

Angular 的啟動方式

直接運行這段代碼,我們會發現第二個 ng-app 並沒有工作,或者說第二個 ng-app 並沒有自動啟動。為什麼會這樣呢?相信對 Angular 瞭解比較多的人會馬上給出答案,那就是 Angular 只會自動啟動找到的第一個 ng-app,後面其他的 ng-app 沒有機會自動啟動。

如何解決這個問題呢?我們可以手動啟動後面沒有啟動的ng-app。舉個例子:

hello_world.html

 1 <!doctype html> 2 <html> 3 <body> 4   <div ng-controller="MyController"> 5     Hello {{greetMe}}! 6   </div> 7   <script src="http://code.angularjs.org/snapshot/angular.js"></script> 8  9   <script>10     angular.module(‘myApp‘, [])11       .controller(‘MyController‘, [‘$scope‘, function ($scope) {12         $scope.greetMe = ‘World‘;13       }]);14 15     angular.element(document).ready(function() {16       angular.bootstrap(document, [‘myApp‘]);17     });18 </script>19 </body>20 </html>

手動啟動需要注意兩點:一是當使用手動啟動方式時,DOM 中不能再使用 ng-app 指令;二是手動啟動不會憑空建立不存在的 module,因此需要先載入 module 相關的代碼,再調用angular.bootstrap方法。如果你對 Angular 的啟動方式還是不太明白的話,請參考官方文檔。 

現在關於Angular 啟動的問題解決了,可能有的人會問,如果我的頁面中在不同的地方有很多需要手動啟動的 ng-app 怎麼辦呢?難道我要一遍一遍的去調用angualar.bootstrap嗎?這樣的代碼看上去總覺得哪裡不對,重複的代碼太多了,因此我們需要重構一下。這裡重構的方式可能多種多樣,我們採用的方式是這樣的:

main.js

1 $(function(){2   $(‘[data-angular-app]‘).each(function(){3     var $this = $(this)4     angular.bootstrap($this, [$this.attr(‘data-angular-app‘]))5   })6 })

先將代碼中所有的 ng-app 改為 data-angular-app,然後在 document ready 的時候用 jQuery 去解析 DOM 上所有的data-angular-app屬性,拿到 ng-app 的值,最後用手動啟動的方式啟動 Angular。 

Mini Pub-Sub

趟過了一個坑,我們再回到另一個問題上,如何才能在多個 ng-app 中通訊呢?畢竟它們都已經不在相同的 context 中了。這裡需要說明一下,在 Angular 中 ng-app 在 DOM 結構上是不能有嵌套關係的。每個 ng-app 都有自己的 rootScope,我們不能再直接使用 Angular 自己提供的一些 API 了。因為不管是 $broadcast 還是$emit,它們都不能跨越不同的 ng-app。相信瞭解發布訂閱機制的人(尤其是做過 WinForm 程式的人)能夠很快想到一種可行的解決方案,那就是我們自己實現一個簡易的發布訂閱機制,然後通過發布訂閱自訂的事件在不同的 ng-app 中通訊。

聽起來感覺很簡單,實際上做起來也很簡單。Talk is cheap, show me the code.

首先我們需要一個管理事件的地方,詳細的解釋[參考 StackOverflow 上的這個帖(http://stackoverflow.com/a/2969692/3049524)。

event_manager.js

 1 (function($){ 2   var eventManager = $({}) 3  4   $.subscribe = function(){ 5     eventManager.bind.apply(eventManager, fn) 6   } 7  8   $.publish = function(){ 9     eventManager.trigger.apply(eventManager, fn)10   }11 })(jQuery)

暫時只實現了兩個 API,一個subscribe用於訂閱事件,publish用於發布事件。 

訂閱事件:

1 $.subscribe(‘user_rank_changed‘, function(event, data){2   $timeout(function(){3     // do something4   })5 })

發布事件: 

1 $.publish(‘user_rank_changed‘, {/*some data*/})

這裡用了一個小 trick,因為我們的事件發布訂閱都是採用的 jQuery 方式,為了讓 Angular 能夠感知到 scope 上資料的變化,我們將整個回呼函數包在了$timeout中,由 JavaScript 自己放到時間迴圈中去等到閒置時候開始執行,而不是使用$scope.$apply()方法,是因為有些時候直接調用該方法會給我們帶來另一個Error: $digest already in progress的錯誤。雖然也可以用$rootScope.$$phase || $rootScope.$apply();這種方式來規避,但是個人認為還是略顯 tricky,沒有$timeout 的方式優雅。 

因為我們用的是原生的 JavaScript 的事件機制,所以即使我們的 Controller 或者 Service 處於不同的 ng-app 中,我們也能夠輕鬆地相互傳輸資料了。

改進原則

在Angular 的單頁面應用中,我們盡量一個應用只有一個 ng-app,然後通過 Module 對業務進行模組劃分,而不是 ng-app。不到萬不得已,不要和 jQuery 混著用,總是使用 Angular 的思維方式進行開發,否則一不小心就會掉進資料不同步的坑中。

  標籤: AngularJS

多個 ng-app 中 Controllers & Services 之間的通訊

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.