跳到主要内容

Angularjs的作用域以及作用域之间的通信(事件广播)

·3061 字·7 分钟

最近做不同controller之间的调用,用到了事件广播,顺便总结一下。

最近在开发过程中使用AngularJs,由于业务需求,需要使用A的controller调用B的controller中的方法。想到了在A中使用$rootScope.$broadcast(name,args)进行广播,在B中使用$scope.$on(name,function(event,data){})进行监听并调用B中方法。顺便总结一下AngularJs的作用域以及作用域之间的通信(事件广播)。

  • 场景:A的controller调用B的controller中的方法

    • A中代码

      //第一个参数是广播名,后面的参数是一些传参args
      //注意要用全局scope调用-->$rootScope
      //需要依靠其他事件(如ng-click等)进行触发
      $rootScope.$broadcast('judge', '1');
      
    • B中代码

      //同样适用全局scope调用-->$rootScope
      //广播名judge对应上即可
      //$scope.changePwd();就是B中的一个方法
      $scope.$on('judge', function (event, data) {
      	$scope.changePwd();
      });
      
一、作用域(父子作用域、兄弟作用域) #
  • AngularJs中的作用域($scope)结构和DOM结构非常相似,也是一个层次分明的树状结构。它有一个根作用域$rootscope(对应Angular应用或者ng-app),其他作用域是嵌套在根作用域下面的。不同的作用域之间是相互隔离的,通过常规手段是无法互相访问变量及方法的。

  • AngularJs的$scope之间都遵循js中对象原型继承方式,当子作用域中没有该对象时,默认向上级作用域(父作用域)寻找,直到找到或者到达$rootscope为止,当子作用域有该对象时,使用子作用域中的对象。(注:这里的父作用域包含直接父级和祖先,子作用域包含直接子级和更下层级)

  • 例子:

    • 例子一:

      <body ng-app="myApp">
          <div ng-controller="fatherCtrl">
              <input type="text" ng-model="name" ng-change="nameOnChange()">
              <h1>Father: {{name}}!</h1>
              <div ng-controller="sonCtrl">
                   <input type="text" ng-model="name" ng-change="nameOnChange()"> 
                   <h1>Son: {{name}}</h1>
              </div>
          </div>
      </body>
      

      其中fatherCtrl 和 sonCtrl 里都使用了 name,但sonCtrl中并没有定义name。

      var app = angular.module ('myApp', []);
          app.controller('fatherCtrl', function ($scope) {
              $scope.name = "father";
              $scope.$watch("name",function () {
                  console.log("fatherScope:"+$scope.name)
              })
      
          });
          app.controller('sonCtrl', function ($scope) {
              $scope.$watch("name",function () {
                  console.log("sonScope:"+$scope.name)
              })
          });
      

      这时sonCtrl 直接读取 fatherCtrl 中的 name,改变fatherCtrl中name的值,sonCtrl显示的值也会同步改变。

      但当通过view同步改变sonCtrl中name的值时,sonCtrl中会重新创建一个name的对象,faterCtrl中的值不会改变。

    • 例子二:

      • 1.父子作用域
      <div ng-controller="ParentCtrl">
          <div ng-controller="ChildCtrl"></div>
      </div>
      

      当我们的controller层级关系是这种时,ChildCtrl就是子controller,ParentCtrl就是父controller,他们之间的作用域关系就是父子作用域。

      app.controller("ParentCtrl", [ '$scope', function($scope){
          $scope.title = "I'm the Parent.";
       }]);
      app.controller("ChildCtrl", [ '$scope', function($scope){
          $scope.title = "I'm the Child.";
       }]);
      

      此时父子controller中都有$scope.title这个变量,此时如果子controller想使用父作用域中的变量的话:

      $scope.$parent.title="Get parent's title";
      

      如果反过来,父controller想访问子controller中的变量或方法的话:

      $scope.$$childHead.title="Get the first child's title"; //得到第一个子作用域中的变量
      $scope.$$childTail.title="Get the last child;s title";//得到最后一个子作用域中的变量
      

      这里要特殊说一点,父controller可以有很多个子controller,但是一个子controller只能有一个父controller,一个controller可以有很多个兄弟controller。

      • 2.兄弟作用域
      <div ng-controller="FirstCtrl"></div>
      <div ng-controller="SecondCtrl"></div>
      

      此时FirstCtrl和SecondCtrl是兄弟关系,如果在FirstCtrl中想访问SecondCtrl中的变量或方法的话:

      $scope.$$nextSibling.title="Get little brother's title";
      

      如果在SecondCtrl中想访问FirstCtrl中的变量的话:

      $scope.$$prevSibling.title="Get big brother's title";
      
二、作用域之间的通信 #
  • 1.如何在作用域之间通信?

    • 1.创建一个单例服务,然后通过这个服务处理所有子作用域的通信。
    • 2.通过作用域中的事件处理通信。但是这种方法有一些限制;你并不能广泛的将事件传播到所有监控的作用域中。你必须选择是与父级作用域或者子作用域通信。
  • 2.$on$emit$broadcast使得event、data在controller之间的传递变的简单。

    • 1.$emit:子传父,传递event与data;

      $scope.$emit('name', 'args');
      
    • 2.$broadcast:父传子,传递event与data;

      $scope.$broadcast('name', 'args');
      
    • 3.$on:监听或接收数据,用于接收event与data;

      $scope.$on('name', function(event,data){});
      

      $broadcast$emit事件必须依靠其他事件(ng-click等)进行触发。

      值得注意的是:以上事件的主语是 $scope, 因为所有的事件其实都是作用在scope上的。 (一般$broadcast使用$rootscope调用)

      • $on的方法中的event事件参数,其对象的属性和方法如下:

        事件属性/方法功能性说明
        event.targetScope获取传播事件的作用域
        event.currentScope获取接收事件的作用域
        event.name传播的事件的名称
        event.stopPropagation()阻止事件进行冒泡传播,仅在$emit事件中有效(即取消通过$emit触发的事件的进一步传播)
        event.preventDefault()阻止默认事件的发生(preventDefault把defaultPrevented标志设置为true。尽管不能停止事件的传播,我们可以告诉子作用域无需处理这个事件。也就是说,可以安全地忽略他们。)
        event.defaultPrevented布尔值。如果调用了preventDefault事件则返回true。

        $on()函数返回了一个反注册函数,我们可以调用它来取消监听器。

    • 3.$on$emit$broadcast详解:

      事件传播的方向示意图如下所示:

      • 1.$emit

        • 该服务贯穿作用域发出一个向上的事件,该事件的生命周期开始于emit被启动的地方,事件一直朝着根作用域传递,传递期间会通知那些注册在作用域上的监听器,在这期间,作用域中的监听器接收到通知,获取事件,但是不会注销事件,事件继续往下传播。

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
              <script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
          </head>
          <body ng-app="myApp">
          <div ng-controller="fatherCtrl">
              <!--fatherCtrl可以得到-->
              <input type="text" ng-model="name" ng-change="nameOnChange()">
              <h1>Father: {{name}}!</h1>
              <div ng-controller="sonCtrl">
                  <!--sonCtrl可以得到-->
                  <input type="text" ng-model="name" ng-change="nameOnChange()">
                  <h1>Son: {{name}}</h1>
                  <div ng-controller="innerCtrl">
                      <input type="text" ng-model="name" ng-change="nameOnChange()">
                      <h1>inner: {{name}}</h1>
                  </div>
              </div>
              <div ng-controller="broCtrl">
                  <!--broCtrl得不到-->
                  <input type="text" ng-model="name" ng-change="nameOnChange()">
                  <h1>brother: {{name}}</h1>
              </div>
          </div>
          
          <script>
              var app = angular.module('myApp', []);
              app.controller('fatherCtrl', function ($scope) {
                  $scope.name = "father";
                  $scope.$on('test', function (e, newName) {
                      $scope.name = newName;
                  });
              });
              app.controller('sonCtrl', function ($scope) {
                  $scope.name = "son";
                  $scope.$on('test', function (e, newName) {
                      $scope.name = newName;
                  });
              });
              app.controller('broCtrl', function ($scope) {
                  $scope.name = "brother";
                  $scope.$on('test', function (e, newName) {
                      $scope.name = newName;
                  });
              });
              app.controller('innerCtrl', function ($scope) {
                  $scope.name = "inner";
                  $scope.nameOnChange = function () {
                      $scope.$emit('test', $scope.name);
                  }
              });
          </script>
          </body>
          
          
          </html>
          
      • 2.$broadcast

        • 该服务发布一个向下的事件给作用域中的所有子节点,该事件的生命周期也是从broadcast被启动开始。下面的所有子作用域都会接收到通知。之后,事件向下传播,在这期间,作用域中的监听器接收到通知,获取事件,但是不会注销事件,事件继续往下传播。

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
              <script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
          </head>
          <body ng-app="myApp">
          <div ng-controller="fatherCtrl">
              <!--fatherCtrl得不到-->
              <input type="text" ng-model="name" ng-change="nameOnChange()">
              <h1>Father: {{name}}!</h1>
              <div ng-controller="sonCtrl">
                  <input type="text" ng-model="name" ng-change="nameOnChange()">
                  <h1>Son: {{name}}</h1>
                  <div ng-controller="outCtrl">
                      <!--outCtrl可以得到-->
                      <input type="text" ng-model="name" ng-change="nameOnChange()">
                      <h1>out in son: {{name}}</h1>
                      <div ng-controller="innerCtrl">
                          <!--innerCtrl可以得到-->
                          <input type="text" ng-model="name" ng-change="nameOnChange()">
                          <h1>inner in out: {{name}}</h1>
                      </div>
                  </div>
              </div>
              <div ng-controller="broCtrl">
                  <!--broCtrl得不到-->
                  <input type="text" ng-model="name" ng-change="nameOnChange()">
                  <h1>brother: {{name}}</h1>
              </div>
          </div>
          
          <script>
              var app = angular.module('myApp', []);
              app.controller('fatherCtrl', function ($scope) {
                  $scope.name = "father";
                  $scope.$on('test', function (e, newName) {
                      $scope.name = newName;
                  });
              });
              app.controller('sonCtrl', function ($scope) {
                  $scope.name = "son";
                  $scope.nameOnChange = function () {
                      $scope.$broadcast('test', $scope.name);
                  }
              });
              app.controller('broCtrl', function ($scope) {
                  $scope.name = "brother";
                  $scope.$on('test', function (e, newName) {
                      $scope.name = newName;
                  });
              });
              app.controller('innerCtrl', function ($scope) {
                  $scope.name = "inner";
                  $scope.$on('test', function (e, newName) {
                      $scope.name = newName;
                  });
              });
              app.controller('outCtrl', function ($scope) {
                  $scope.name = "out";
                  $scope.$on('test', function (e, newName) {
                      $scope.name = newName;
                  });
              });
          </script>
          </body>
          
          
          </html>
          
      • 3.$on

        • 该服务监听指定类型的事件,获取从emit或者broadcast发布的事件。
      • 注意:

        • 1.如果在作用域中没有父子关系存在,可以在控制器中注入$rootScope、使用$broadcast服务向下传播事件(但这个慎用),但是不能通过$emit向上传播事件。
        • 2.在作用域中存在父子关系时,可以也仅可以由子控制器使用$emit服务向上传播事件,同时父作用域中的控制监听器可以注销事件。