Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AngularJS 中 Controller 之间的通信 #8

Open
huangtengfei opened this issue Dec 16, 2015 · 11 comments
Open

AngularJS 中 Controller 之间的通信 #8

huangtengfei opened this issue Dec 16, 2015 · 11 comments

Comments

@huangtengfei
Copy link
Owner

用 Angular 进行开发,基本上都会遇到 Controller 之间通信的问题,本文对此进行一个总结。

在 Angular 中,Controller 之间通信的方式主要有三种:

1)作用域继承。利用子 Controller 控制父 Controller 上的数据。(父 Controller 中的数据要为引用类型,不能是基本类型,原因参见 AngularJS中的作用域 一文)

2)注入服务。把需要共享的数据注册为一个 service,在需要的 Controller 中注入。

3)基于事件。利用 Angular 的事件机制,使用 $on$emit$boardcast

其中,作用域继承仅限于上下级之间的通信,注入服务和基于事件的机制可以实现任意级别的 Controller 通信。

这里 可以查看下面栗子的演示。

作用域继承

原理在 作用域 一文中有讲解,这里直接上栗子。

页面:

<div ng-controller="parentCtrl">
  <p>data in parent controller : {{data.name}}</p>
  <div ng-controller="childCtrl">
    <input type="text" ng-model="data.name">
  </div>
</div>

控制器:

angular.module('demo', [])

.controller('parentCtrl', ['$scope', function($scope){
    $scope.data = {
        name: 'htf'
    }
}])

.controller('childCtrl', ['$scope', function($scope){

}])

以上是父 Controller 中的数据是引用类型的情况。如果父 Controller 中的数据是基本类型,可通过 $scope.$parent.data 访问。

很显然,这种方式仅适用于父子级间 Controller 的通信。

注入服务

在 Angular 中,服务是一个单例,所以在服务中生成一个对象,该对象就可以利用依赖注入的方式在所有的控制器中共享。

看个栗子,先定义一个 service :

angular.module('demo')
    .factory('Data', function(){
        return {
            name: 'htf'
        };
    })

页面:

<div ng-controller="childCtrl1">
  <h3>data in child controller 1 : {{data.name}}</h3>
  <input class="form-control" type="text" ng-model="data.name">
</div>
<div ng-controller="childCtrl2">
  <h3>data in child controller 2 : {{data.name}}</h3>
  <input class="form-control" type="text" ng-model="data.name">
</div>

控制器:

.controller('childCtrl1', ['$scope', 'Data', function($scope, Data){
    $scope.data = Data;
}])

.controller('childCtrl2', ['$scope', 'Data', function($scope, Data){
    $scope.data = Data;
}])

这种方式适用于任何需要通信的 Controller 之间。

基于事件

Angular 为 $scope 提供了冒泡和隧道机制,$broadcast 会把事件广播给所有子 Controller,而 $emit 则会将事件冒泡传递给父 Controller,$on 则是 Angular 的事件监听函数,利用这三者,可以实现上下级和同级(需要构造一个共同的父级 Controller)之间的通信。

上下级之间

这种情况下比较简单。

如果是子 Controller 往父 Controller 上发送事件(从作用域往上发送事件),使用 scope.$emit

$scope.$emit("someEvent", {});

如果是父 Controller 往子 Controller 上发送事件(从作用域往下发送事件),使用 scope.$broadcast

$scope.$broadcast("someEvent", {});

无论是 $emit 还是 $broadcast 发送的事件,都用 $scope.$on 接收:

$scope.$on("someEvent", function(event, data) {
    // 这里取到发送过来的数据 data
});

同级之间

同级之间利用事件通信有两种方法。一种是利用上下级之间事件传播的变形,另一种是借助 $rootScope

借助父 controller

先看第一种,在子 Controller 中向父 Controller 触发一个事件,然后在父 Controller 中监听事件,再广播给子 Controller ,这样通过事件携带的参数,实现了数据经过父 Controller,在同级 Controller 之间传播。

但是要注意,通过父 Controller 作为中介进行传递的话,子 Controller 触发的事件名和父 Controller 广播用的事件名不能一样,否则会进入死循环。

看代码:

<div ng-controller="outerCtrl">
  <h3>data in outer controller: {{name}}</h3>
  <div ng-controller="innerCtrl1">
    <input class="form-control" type="text" ng-model="name" ng-change="change()">
  </div>
  <div ng-controller="innerCtrl2">
    <input class="form-control" type="text" ng-model="name" ng-change="change()">
  </div>
</div>

关键部分在控制器:

.controller('outerCtrl', ['$scope', function($scope){
    $scope.name = 'htf';
    $scope.$on('dataChanged', function(event, data){
        $scope.name = data;
        // 2. 父 Ctrl 监听到 dataChanged 时间后,触发 changeData 事件
        $scope.$broadcast('changeData', data);
    })
}])

.controller('innerCtrl1', ['$scope', function($scope){
    $scope.change = function(){
        // 1. 子 Ctrl1 中数据改变之后触发 dataChanged 事件
        $scope.$emit('dataChanged', $scope.name);
    }
    $scope.$on('changeData', function(event, data){
        $scope.name = data;
    })
}])

.controller('innerCtrl2', ['$scope', function($scope){
    $scope.change = function(){
        $scope.$emit('dataChanged', $scope.name);
    }
    // 3. 监听到 changeData 事件后,改变子 Ctrl2 中 数据
    $scope.$on('changeData', function(event, data){
        $scope.name = data;
    })
}])

借助 $rootScope

每个 Angular 应用默认有一个根作用域 $rootScope, 根作用域位于最顶层,从它往下挂着各级作用域。

所以,如果子控制器直接使用 $rootScope 广播和接收事件,那么就可实现同级之间的通信。

看栗子:

<div ng-controller="innerCtrlA">
    <input class="form-control" type="text" ng-model="name" ng-change="change()">
</div>
<div ng-controller="innerCtrlB">
    <input class="form-control" type="text" ng-model="name" ng-change="change()">
</div>

控制器:

.controller('innerCtrlA', ['$scope', '$rootScope', function($scope, $rootScope){
    $scope.change = function(){
        // 广播事件
        $rootScope.$broadcast('nameChanged', $scope.name);
    }
    $rootScope.$on('nameChanged', function(event, data){
        $scope.name = data;
    })
}])

.controller('innerCtrlB', ['$scope', '$rootScope', function($scope, $rootScope){
    $scope.change = function(){
        $rootScope.$broadcast('nameChanged', $scope.name);
    }
    // 监听事件
    $rootScope.$on('nameChanged', function(event, data){
        $scope.name = data;
    })
}])

参考

  1. 作用域与事件
  2. Angularjs Controller 间通信机制
  3. Communicating Between Controllers In Angular
  4. AngularJS控制器controller如何通信?
  5. controller 间共享数据
@EtachGu
Copy link

EtachGu commented Apr 13, 2016

楼主好,按照您说的方法,使用rootScope进行广播,会出现如下错误,是为什么呢?
TypeError: $rootScope.$broadcast is not a function

@huangtengfei
Copy link
Owner Author

@EtachGu 有在 Controller 里面注入 $rootScope 吗?

@EtachGu
Copy link

EtachGu commented Apr 14, 2016

有啊注入$rootScope, 但是还是报错! 另外,有个问题想请教一下,如果使用ng-view, 会出现跨Scope的两个Ctrl1与Ctrl2之间的通信问题,Ctrl2中的$rootScope不等于Ctrl1中的$rootScope,这个怎么解决呢?

index.html :

...
<div ng-view></div>
 <div ng-controller="Ctrl1">
    <button ng-click="click()"> B</button>
</div>

...

sub.html :

<div ng-controller="Ctrl2">
  <p>{{  content }}<p>
  </div>

JS :

var myApp= angular.module('myApp', [
  'ngRoute'
]);
myApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/sub', {
        templateUrl: 'sub.html'
      }).
      otherwise({
        redirectTo: '/sub'
      });
  }]);
myApp.controller('Ctrl1',['$scope','$rootScope',function($scope,$rootScope){
    $scope.click = function(){
      $rootScope.$broadcast('Update',  "CCC");
  }
}]);
myApp.controller('Ctrl2',['$scope','$rootScope',function($scope,$rootScope){
    $scope.content = "AAA";
    $rootScope.$on('Update',function(){
       $scope.content = c;
    });
}]);

@huangtengfei
Copy link
Owner Author

@EtachGu 前面注入$rootScope 还是报错的相关代码你贴出来我看下。

后面那个问题,按照你代码这样写,是没问题的,如果你在 $rootScope.$on 的地方是下面这样写的话:

$rootScope.$on('Update',function(e, c){
    $scope.content = c;
});

回调里面,第二个参数是 data

@EtachGu
Copy link

EtachGu commented Apr 14, 2016

我刚刚测试了一下,只用angular按照rootScope的方法通信没问题,估计是我用了bootstrap 和 OpenLayer 等之后就出问题了。

@EtachGu
Copy link

EtachGu commented Apr 14, 2016

我知道问题了,是

myApp.controller('Ctrl1',['$scope','$document','$rootScope',function($scope,$rootScope){
    $scope.click = function(e, c){
      $rootScope.$broadcast('Update',  "CCC");
  }
}]);

多写了一个$document,然后对应到了function参数$rootScope了,
解决方法,删掉$document

@huangtengfei
Copy link
Owner Author

@EtachGu 是的,依赖性跟参数必须一一对应

@sky-admin
Copy link

楼主好,请问service,factory和Providers有啥区别?我的数据源是异步获取的,应该用哪种方式?

@huangtengfei
Copy link
Owner Author

@sky-admin 三者的区别你可以看看这篇 AngularJS 中 Provider 的用法及区别,三者都可用于异步获取数据,通常情况下直接用 service 就可以

@HZYou
Copy link

HZYou commented Mar 3, 2017

楼主很棒,解决了我的疑惑,感谢

@sapphoo
Copy link

sapphoo commented Jun 6, 2018

很全的总结,多谢lz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants