• 多个 ng-app 中 Controllers & Services 之间的通信


    原文发布在个人独立博客上,链接: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 = from
    15   })
    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 = from
    30   })
    31 })
    32 
    33 app.controller('GrandChildCtrl', function($scope) {
    34   $scope.message = ''
    35 
    36   $scope.$on('msg_triggered', function(event, from){
    37     $scope.message = from
    38   })
    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 = from
    53   })
    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 angular
    12   .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 something
    4   })
    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 的思维方式进行开发,否则一不小心就会掉进数据不同步的坑中。

  • 相关阅读:
    Coursera台大机器学习课程笔记8 -- Linear Regression
    近两年跟踪速度较快的算法小结(转)
    hdu 4278 Faulty Odometer
    hdu 2571 命运
    hdu 6168 Numbers
    Codeforces 888C:K-Dominant Character
    poj 3061 Subsequence
    poj 1852 Ants
    1115. Counting Nodes in a BST (30)
    1064. Complete Binary Search Tree (30)
  • 原文地址:https://www.cnblogs.com/wenJiaQi/p/6359091.html
Copyright © 2020-2023  润新知