多个 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
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 之间的通信的更多相关文章
- android中四大组件之间相互通信
好久没有写有关android有关的博客了,今天主要来谈一谈android中四大组件.首先,接触android的人,都应该知道android中有四大组件,activity,service,broadca ...
- vue2.0中父子组件之间的通信总结
父组件: 子组件: 接受父组件的信息: 向父组件发送事件: (其中slot是插槽,可以将父组件中的<p>123</p>插入进来,如果父组件没有插入的内容,则显示slot内部的内 ...
- vue中兄弟之间组件通信
我们知道Vue中组件之间的通信有很多方式,父子之间通信比较简单,当我们使用vuex时候,兄弟组件之间的通信也很好得到解决 当我们项目较小时候,不使用vuex时候Vue中兄弟组件之间的通信是怎样进行的呢 ...
- vue2.0s中eventBus实现兄弟组件通信
在vue1.0中,组件之间的通信主要通过vm.$dispatch沿着父链向上传播和用vm.$broadcast向下广播来实现.然而在vue2.0中,已经废除了这种用法. vuex加入后,对组件之间的通 ...
- 走进AngularJs(二) ng模板中常用指令的使用方式
通过使用模板,我们可以把model和controller中的数据组装起来呈现给浏览器,还可以通过数据绑定,实时更新视图,让我们的页面变成动态的.ng的模板真是让我爱不释手.学习ng道路还很漫长,从模板 ...
- 我刚知道的WAP app中meta的属性
之前我一直做的都是WEB前端开发,来北京以后面试了一个移动前端开发,WAP前端开发. 其实在原来公司的时候也做过这方面的开发,可面试的时候面试官问我,要想强制让文档与设备的宽度保持1:1,mate标签 ...
- 我刚知道的WAP app中meta的属性(转载)
之前我一直做的都是WEB前端开发,来北京以后面试了一个移动前端开发,WAP前端开发. 其实在原来公司的时候也做过这方面的开发,可面试的时候面试官问我,要想强制让文档与设备的宽度保持1:1,mate标签 ...
- Android中BroadCast与Activity之间的通信
在看本文之前,假设你对于Android的广播机制不是非常了解.建议先行阅读我转载的一篇博文:图解 Android 广播机制. 因为本案例比較简单,故直接在此贴出代码,不做过多的阐述. 先上效果截图: ...
- 通过AIDL在两个APP之间Service通信
一.项目介绍 [知识准备] ①Android Interface definition language(aidl,android接口定义语言),其目的实现跨进程的调用.进程是程序在os中执行的载体, ...
随机推荐
- Centos7系统下修改主机名操作笔记
习惯了在Centos6系统下修改主机名的操作,但是Centos7下修改主机名的操作却大不相同!操作笔记如下: 在CentOS中,有三种定义的主机名:静态的(static),瞬态的(transient) ...
- Linux磁盘空间被占用问题 (分区目录占用空间比实际空间要大: 资源文件删除后, 空间没有真正释放)
问题说明:IDC里的一台服务器的/分区使用率爆满了!已达到100%!经查看发现有个文件过大(80G),于是在跟有关同事确认后rm -f果断删除该文件.但是发现删除该文件后,/分区的磁盘空间压根没有释放 ...
- 分布式监控系统Zabbix-3.0.3-完整安装记录(1)
分布式监控系统Zabbix-3.0.3的安装记录 环境说明zabbix-server:192.168.1.30 #zabbix的服务端(若要监控本机,则需要配置本机的Zabbix agent, ...
- Nginx入门【转】
原文地址:http://blog.csdn.net/u012486840/article/details/53098890 1.静态HTTP服务器 首先,Nginx是一个HTTP服务器,可以将服务器上 ...
- vue 动态修改 css
<div v-for="i in resultDate" v-if="i.ProjectId>='4'" @click=EveyTesttInfo( ...
- 个人阅读作业——软件工程M1/M2的总结
临近学期末,本学期的软件工程课也已经结束了,在此我对软件工程课中,我们团队M1和M2开发阶段中,我做的工作做一个总结 我是DEV,主要工作是等着上级给我分配任务,但是很多时候如果这个活我不干,其他人就 ...
- 在centos7虚拟机上挂载镜像,并设置yum源(包括遇到的问题)
挂载镜像方法很简单: mkdir /etc/a mount /dev/cdrom /etc/a 查看挂载情况 : df -h 修改yum源文件 : 先把 CentOS-Base.repo 文件名改一 ...
- 软件工程_7th weeks
内聚和耦合(学习笔记) 一.内聚 内聚是一个模块内部各成分之间相关联程度的度量.把内聚按紧密程度从低到高排列次序为: 1.偶然内聚:指一个模块内各成分为完成一组功能而组合在一起,它们相互之间即使有关系 ...
- Delphi通过查找字符定位TADOQuery数据的位置
通过TADOQuery的方法Locate,输入字符,查找到定位到对应的数据位置,优点快速定位,缺点是只匹配查找到的和第一个位置,无法连续定位下一个! //定位qrymembertype.Locate( ...
- jquery 祖先、子孫、同級
jquery向上遍歷,獲取祖先元素 parent()獲取選中元素的父 parents()獲取選中元素的所有的祖先節點,一直到文檔的根元素<html> parentUntil(“元素1”)獲 ...