原文发布在个人独立博客上,链接:http://pengisgood.github.io/2016/01/31/communication-between-multiple-angular-apps/

通常情况下,在 Angular 的单页面应用中不同的 Controller 或者 Service 之间通信是一件非常容易的事情,因为 Angular 已经给我们提供了一些便利的方法:$on$emit$broadcast

在这里用一个简单的例子来演示一下这三个方法的用途,完整版代码也可以参考这里

style.css

 body {
background-color: #eee;
} #child {
background-color: red;
} #grandChild {
background-color: yellow;
} #sibling {
background-color: pink;
} .level {
border: solid 1px;
margin: 5px;
padding: 5px;
}

index.html

 <body ng-app="app" ng-controller="ParentCtrl" class='level'>
<h2>Parent</h2>
<button ng-click="broadcastMsg()">Broadcast msg</button>
<button ng-click="emitMsg()">Emit msg</button>
<pre>Message from: {{message}}</pre>
<div id='child' ng-controller="ChildCtrl" class='level'>
<h2>Child</h2>
<button ng-click="broadcastMsg()">Broadcast msg</button>
<button ng-click="emitMsg()">Emit msg</button>
<pre>Message from: {{message}}</pre> <div id='grandChild' ng-controller="GrandChildCtrl" class='level'>
<h2>Grand child</h2> <pre>Message from: {{message}}</pre>
</div>
</div>
<div id='sibling' ng-controller="SiblingCtrl" class='level'>
<h2>Sibling</h2>
<button ng-click="broadcastMsg()">Broadcast msg</button>
<button ng-click="emitMsg()">Emit msg</button>
<pre>Message from: {{message}}</pre>
</div>
</body>

app.js

 var app = angular.module('app', [])
app.controller('ParentCtrl', function($scope) {
$scope.message = '' $scope.broadcastMsg = function() {
$scope.$broadcast('msg_triggered','parent')
} $scope.emitMsg = function() {
$scope.$emit('msg_triggered','parent')
} $scope.$on('msg_triggered', function(event, from){
$scope.message = from
})
}) app.controller('ChildCtrl', function($scope) {
$scope.message = ''
$scope.broadcastMsg = function() {
$scope.$broadcast('msg_triggered','child')
} $scope.emitMsg = function() {
$scope.$emit('msg_triggered','child')
} $scope.$on('msg_triggered', function(event, from){
$scope.message = from
})
}) app.controller('GrandChildCtrl', function($scope) {
$scope.message = '' $scope.$on('msg_triggered', function(event, from){
$scope.message = from
})
}) app.controller('SiblingCtrl', function($scope) {
$scope.message = ''
$scope.broadcastMsg = function() {
$scope.$broadcast('msg_triggered','sibling')
} $scope.emitMsg = function() {
$scope.$emit('msg_triggered','sibling')
} $scope.$on('msg_triggered', function(event, from){
$scope.message = from
})
})

在上面的例子中我们可以看出,利用 Angular 已有的一些 API 能够很方便的在不同 Controller 之间通信,仅需要广播事件即可。

上面的代码之所以能工作,是因为我们一直都有着一个前提,那就是这些 Controller 都在同一个 ng-app 中。那么,如果在一个页面中存在多个 ng-app 呢?(尽管并不推荐这样做,但是在真实的项目中,尤其是在一些遗留项目中,仍然会遇到这种场景。)

先看一个简单的例子:

style.css

 .app-container {
height: 200px;
background-color: white;
padding: 10px;
} pre {
font-size: 20px;
}

index.html

 <body>
<div class="app-container" ng-app="app1" id="app1" ng-controller="ACtrl">
<h1>App1</h1>
<pre ng-bind="count"></pre>
<button ng-click="increase()">Increase</button>
</div>
<hr />
<div class="app-container" ng-app="app2" id="app2" ng-controller="BCtrl">
<h1>App2</h1>
<pre ng-bind="count"></pre>
<button ng-click="increase()">Increase</button>
</div>
</body>

app.js

 angular
.module('app1', [])
.controller('ACtrl', function($scope) {
$scope.count = 0; $scope.increase = function() {
$scope.count += 1;
};
}); angular
.module('app2', [])
.controller('BCtrl', function($scope) {
$scope.count = 0; $scope.increase = function() {
$scope.count += 1;
};
});

Angular 的启动方式

直接运行这段代码,我们会发现第二个 ng-app 并没有工作,或者说第二个 ng-app 并没有自动启动。为什么会这样呢?相信对 Angular 了解比较多的人会马上给出答案,那就是 Angular 只会自动启动找到的第一个 ng-app,后面其他的 ng-app 没有机会自动启动。

如何解决这个问题呢?我们可以手动启动后面没有启动的ng-app。举个例子:

hello_world.html

 <!doctype html>
<html>
<body>
<div ng-controller="MyController">
Hello {{greetMe}}!
</div>
<script src="http://code.angularjs.org/snapshot/angular.js"></script> <script>
angular.module('myApp', [])
.controller('MyController', ['$scope', function ($scope) {
$scope.greetMe = 'World';
}]); angular.element(document).ready(function() {
angular.bootstrap(document, ['myApp']);
});
</script>
</body>
</html>

手动启动需要注意两点:一是当使用手动启动方式时,DOM 中不能再使用 ng-app 指令;二是手动启动不会凭空创建不存在的 module,因此需要先加载 module 相关的代码,再调用angular.bootstrap方法。如果你对 Angular 的启动方式还是不太明白的话,请参考官方文档

现在关于Angular 启动的问题解决了,可能有的人会问,如果我的页面中在不同的地方有很多需要手动启动的 ng-app 怎么办呢?难道我要一遍一遍的去调用angualar.bootstrap吗?这样的代码看上去总觉得哪里不对,重复的代码太多了,因此我们需要重构一下。这里重构的方式可能多种多样,我们采用的方式是这样的:

main.js

 $(function(){
$('[data-angular-app]').each(function(){
var $this = $(this)
angular.bootstrap($this, [$this.attr('data-angular-app']))
})
})

先将代码中所有的 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

 (function($){
var eventManager = $({}) $.subscribe = function(){
eventManager.bind.apply(eventManager, fn)
} $.publish = function(){
eventManager.trigger.apply(eventManager, fn)
}
})(jQuery)

暂时只实现了两个 API,一个subscribe用于订阅事件,publish用于发布事件。

订阅事件:

 $.subscribe('user_rank_changed', function(event, data){
$timeout(function(){
// do something
})
})

发布事件:

 $.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 的思维方式进行开发,否则一不小心就会掉进数据不同步的坑中。

本人的个人独立博客将会逐步迁移至:http://pengisgood.github.io/

http://pengisgood.github.io/2016/01/31/communication-between-multiple-angular-apps/

多个 ng-app 中 Controllers & Services 之间的通信的更多相关文章

  1. android中四大组件之间相互通信

    好久没有写有关android有关的博客了,今天主要来谈一谈android中四大组件.首先,接触android的人,都应该知道android中有四大组件,activity,service,broadca ...

  2. vue2.0中父子组件之间的通信总结

    父组件: 子组件: 接受父组件的信息: 向父组件发送事件: (其中slot是插槽,可以将父组件中的<p>123</p>插入进来,如果父组件没有插入的内容,则显示slot内部的内 ...

  3. vue中兄弟之间组件通信

    我们知道Vue中组件之间的通信有很多方式,父子之间通信比较简单,当我们使用vuex时候,兄弟组件之间的通信也很好得到解决 当我们项目较小时候,不使用vuex时候Vue中兄弟组件之间的通信是怎样进行的呢 ...

  4. vue2.0s中eventBus实现兄弟组件通信

    在vue1.0中,组件之间的通信主要通过vm.$dispatch沿着父链向上传播和用vm.$broadcast向下广播来实现.然而在vue2.0中,已经废除了这种用法. vuex加入后,对组件之间的通 ...

  5. 走进AngularJs(二) ng模板中常用指令的使用方式

    通过使用模板,我们可以把model和controller中的数据组装起来呈现给浏览器,还可以通过数据绑定,实时更新视图,让我们的页面变成动态的.ng的模板真是让我爱不释手.学习ng道路还很漫长,从模板 ...

  6. 我刚知道的WAP app中meta的属性

    之前我一直做的都是WEB前端开发,来北京以后面试了一个移动前端开发,WAP前端开发. 其实在原来公司的时候也做过这方面的开发,可面试的时候面试官问我,要想强制让文档与设备的宽度保持1:1,mate标签 ...

  7. 我刚知道的WAP app中meta的属性(转载)

    之前我一直做的都是WEB前端开发,来北京以后面试了一个移动前端开发,WAP前端开发. 其实在原来公司的时候也做过这方面的开发,可面试的时候面试官问我,要想强制让文档与设备的宽度保持1:1,mate标签 ...

  8. Android中BroadCast与Activity之间的通信

    在看本文之前,假设你对于Android的广播机制不是非常了解.建议先行阅读我转载的一篇博文:图解 Android 广播机制. 因为本案例比較简单,故直接在此贴出代码,不做过多的阐述. 先上效果截图: ...

  9. 通过AIDL在两个APP之间Service通信

    一.项目介绍 [知识准备] ①Android Interface definition language(aidl,android接口定义语言),其目的实现跨进程的调用.进程是程序在os中执行的载体, ...

随机推荐

  1. centos7下安装php+memcached简单记录

    1)centos7下安装php 需要再添加一个yum源来安装php-fpm,可以使用webtatic(这个yum源对国内网络来说恐怕有些慢,当然你也可以选择其它的yum源) [root@nextclo ...

  2. 挂载银行前置机Ukey到windows server2012虚拟机的操作记录

    公司有跟银行对接的金融业务,需要配置银行前置机环境.通过KVM的WebVirtMgr管理平台创建windows server2008虚拟机,安装参考:kvm虚拟化管理平台WebVirtMgr部署-完整 ...

  3. mysqldump数据导出问题和客户端授权后连接失败问题

    1,使用mysqldump时报错(1064),这个是因为mysqldump版本太低与当前数据库版本不一致导致的.mysqldump: Couldn't execute 'SET OPTION SQL_ ...

  4. 使用rem进行自适应页面布局

    设计师给到我们前端的设计稿一般是按照iphone6屏幕(iphone6 两倍屏 设备 分辨率(物理尺寸) 屏幕宽高 PPI 状态栏高度 导航栏高度 标签栏高度 iPhone6 750×1334 px ...

  5. bootstrap是什么

    Bootstrap,来自 Twitter,是目前最受欢迎的前端框架. Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷. 本教程将向您讲解 ...

  6. SCRUM 12.22

    周一,大家现在课程也比较少,今天都在非常努力地写代码. 任务分配如往常一样,我们现在基本将工作的重心放在完善已有的组件上. 成员 任务 彭林江 落实API 牛强 落实意见反馈功能测试 高雅智 测试已完 ...

  7. JS 字符串转换为number

    // '+ "42"' --> + 加上数字字符串可转换成数值 console.log(typeof (+ "42")); // 输出为 number

  8. Install odoo 11(10) on centos7

    https://www.odoo.com/documentation/11.0/setup/install.html https://nightly.odoo.com/ https://www.odo ...

  9. Raphaël - JavaScript Vector Library

    Raphaël http://dmitrybaranovskiy.github.io/raphael/ // ┌──────────────────────────────────────────── ...

  10. HTML的input类型为hidden导致无法reset改字段的value问题

    问题关键:根据HTML规范,hidden是非ui类元素,不接受用户处理.所以form的 reset并不影响它. http://stackoverflow.com/questions/6367793/w ...