关于javascript:一个AngularJS控制器可以调用另一个吗?

Can one AngularJS controller call another?

是否可以让一个控制器使用另一个控制器?

例如:

这个HTML文档只打印由messageCtrl.js文件中的MessageCtrl控制器传递的消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html xmlns:ng="http://angularjs.org/">
<head>
    <meta charset="utf-8" />
    Inter Controller Communication
</head>
<body>
   
        <p>
{{message}}
</p>
   

    <!-- Angular Scripts -->
    <script src="http://code.angularjs.org/angular-0.9.19.js" ng:autobind>
    <script src="js/messageCtrl.js" type="text/javascript">
</body>
</html>

控制器文件包含以下代码:

1
2
3
4
5
6
function MessageCtrl()
{
    this.message = function() {
        return"The current date is:" + new Date().toString();
    };
}

只打印当前日期;

如果我要添加另一个控制器,DateCtrl,它以特定的格式将日期交回MessageCtrl,人们会怎么做呢?DI框架似乎与XmlHttpRequests和访问服务有关。


控制器之间的通信有多种方式。

最好的可能是共享服务:

1
2
3
4
5
6
7
8
9
10
11
function FirstController(someDataService)
{
  // use the data service, bind to template...
  // or call methods on someDataService to send a request to server
}

function SecondController(someDataService)
{
  // has a reference to the same instance of the service
  // so if the service updates state for example, this controller knows about it
}

另一种方法是在作用域上发出事件:

1
2
3
4
5
6
7
8
9
10
function FirstController($scope)
{
  $scope.$on('someEvent', function(event, args) {});
  // another controller or even directive
}

function SecondController($scope)
{
  $scope.$emit('someEvent', args);
}

在这两种情况下,您也可以与任何指令通信。


参见此小提琴:http://jsfiddle.net/simpulton/xqdxg/

同时观看以下视频:控制器之间的通信

Html:

1
2
3
4
5
6
7
8
9
10
  <input ng-model="message">
  <button ng-click="handleClick(message);">LOG</button>



  <input ng-model="message">



  <input ng-model="message">

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
  var sharedService = {};

  sharedService.message = '';

  sharedService.prepForBroadcast = function(msg) {
    this.message = msg;
    this.broadcastItem();
  };

  sharedService.broadcastItem = function() {
    $rootScope.$broadcast('handleBroadcast');
  };

  return sharedService;
});

function ControllerZero($scope, sharedService) {
  $scope.handleClick = function(msg) {
    sharedService.prepForBroadcast(msg);
  };

  $scope.$on('handleBroadcast', function() {
    $scope.message = sharedService.message;
  });        
}

function ControllerOne($scope, sharedService) {
  $scope.$on('handleBroadcast', function() {
    $scope.message = 'ONE: ' + sharedService.message;
  });        
}

function ControllerTwo($scope, sharedService) {
  $scope.$on('handleBroadcast', function() {
    $scope.message = 'TWO: ' + sharedService.message;
  });
}

ControllerZero.$inject = ['$scope', 'mySharedService'];        

ControllerOne.$inject = ['$scope', 'mySharedService'];

ControllerTwo.$inject = ['$scope', 'mySharedService'];


下面是两个共享服务数据的控制器的单页示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!doctype html>
<html ng-app="project">
<head>
    Angular: Service example
    <script src="http://code.angularjs.org/angular-1.0.1.js">
   
var projectModule = angular.module('project',[]);

projectModule.factory('theService', function() {  
    return {
        thing : {
            x : 100
        }
    };
});

function FirstCtrl($scope, theService) {
    $scope.thing = theService.thing;
    $scope.name ="First Controller";
}

function SecondCtrl($scope, theService) {  
    $scope.someThing = theService.thing;
    $scope.name ="Second Controller!";
}
   
</head>
<body>  
   
        {{name}}
        <input ng-model="thing.x"/>        
   

   
        {{name}}
        <input ng-model="someThing.x"/>            
   
</body>
</html>

另请点击:https://gist.github.com/3595424


如果要将一个控制器调用到另一个控制器,有四种方法可用

  • $rootscope.$emit()和$rootscope.$broadcast()。
  • 如果第二个控制器是子控制器,则可以使用父子通信。
  • 使用服务
  • 有点像黑客-借助于angular.element()。
  • 1. $rootScope.$emit() and $rootScope.$broadcast()

    控制器及其作用域会被破坏,但是$rootscope仍然存在于整个应用程序中,这就是为什么我们采用$rootscope的原因,因为$rootscope是所有作用域的父级。

    如果您正在执行从父级到子级的通信,甚至子级希望与其兄弟级通信,则可以使用$broadcast

    如果您正在执行从子级到父级的通信,则没有调用兄弟级,那么您可以使用$rootscope.$emit

    HTML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <body ng-app="myApp">
       
          // ParentCtrl
         
            // Sibling first controller
         
         
            // Sibling Second controller
           
              // Child controller
           
         
       
    </body>

    盎格鲁JS码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     var app =  angular.module('myApp',[]);//We will use it throughout the example
        app.controller('Child', function($rootScope) {
          $rootScope.$emit('childEmit', 'Child calling parent');
          $rootScope.$broadcast('siblingAndParent');
        });

    app.controller('Sibling1', function($rootScope) {
      $rootScope.$on('childEmit', function(event, data) {
        console.log(data + ' Inside Sibling one');
      });
      $rootScope.$on('siblingAndParent', function(event, data) {
        console.log('broadcast from child in parent');
      });
    });

    app.controller('Sibling2', function($rootScope) {
      $rootScope.$on('childEmit', function(event, data) {
        console.log(data + ' Inside Sibling two');
      });
      $rootScope.$on('siblingAndParent', function(event, data) {
        console.log('broadcast from child in parent');
      });
    });

    app.controller('ParentCtrl', function($rootScope) {
      $rootScope.$on('childEmit', function(event, data) {
        console.log(data + ' Inside parent controller');
      });
      $rootScope.$on('siblingAndParent', function(event, data) {
        console.log('broadcast from child in parent');
      });
    });

    在上面的代码控制台$emit'childemit'不会在子同级中调用,它只在父级内部调用,其中$broadcast也会在同级和父级内部调用。这是性能进入操作的地方。$emit是首选的,如果您使用子级到父级通信,因为它跳过了一些脏检查。

    2. If Second controller is child, you can use Child Parent communication

    它是最好的方法之一,如果你想在孩子想与直接父母交流的地方进行子-父通信,那么它就不需要任何类型的$broadcast或$emit,但是如果你想在父-子之间进行通信,那么你必须使用service或$broadcast

    例如HTML:

    1
     

    角晶状体

    1
    2
    3
    4
    5
    6
     app.controller('ParentCtrl', function($scope) {
       $scope.value='Its parent';
          });
      app.controller('ChildCtrl', function($scope) {
       console.log($scope.value);
      });

    每当您使用子到父通信时,AngularJS将搜索子对象内的变量,如果该变量不在子对象内,则它将选择查看父对象控制器内的值。

    3.Use Services

    AngularJS使用服务体系结构支持"分离关注点"的概念。服务是JavaScript函数,只负责执行特定的任务。这使它们成为一个可维护和可测试的单独实体。使用AngularJS的依赖项注入Mecahnism来注入服务。

    盎格鲁JS代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    app.service('communicate',function(){
      this.communicateValue='Hello';
    });

    app.controller('ParentCtrl',function(communicate){//Dependency Injection
      console.log(communicate.communicateValue+" Parent World");
    });

    app.controller('ChildCtrl',function(communicate){//Dependency Injection
      console.log(communicate.communicateValue+" Child World");
    });

    它将提供输出hello child world和hello parent world。根据服务单例的角度文档,依赖于服务的每个组件都会获得对服务工厂生成的单个实例的引用。

    4.Kind of hack - with the help of angular.element()

    此方法通过id/unique类从元素中获取scope()。angular.element()方法返回element,scope()使用一个控制器内部的$scope变量给另一个变量的$scope变量,这不是一个好的做法。

    HTML:

    1
    2
    3
    4
    {{varParent}}
     <span ng-click='getValueFromChild()'>Click to get ValueFormChild</span>
     {{varChild}}
       <span ng-click='getValueFromParent()'>Click to get ValueFormParent </span>

    Angularjs:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    app.controller('ParentCtrl',function($scope){
     $scope.varParent="Hello Parent";
      $scope.getValueFromChild=function(){
      var childScope=angular.element('#child').scope();
      console.log(childScope.varChild);
      }
    });

    app.controller('ChildCtrl',function($scope){
     $scope.varChild="Hello Child";
      $scope.getValueFromParent=function(){
      var parentScope=angular.element('#parent').scope();
      console.log(parentScope.varParent);
      }
    });

    在上面的代码中,控制器在HTML上显示自己的值,当您单击文本时,您将相应地在控制台中获得值。如果单击父控制器范围,浏览器将控制台子控件和viceversa的值。


    如果您希望发出广播事件以在控制器之间共享数据或调用功能,请查看此链接:并检查zbynour的答案(以最大投票数应答)。我引用他的回答!!!!

    如果firstctrl的作用域是secondctrl作用域的父级,您的代码应该通过在firstctrl中用$broadcast替换$emit来工作:

    1
    2
    3
    4
    5
    6
    7
    function firstCtrl($scope){
        $scope.$broadcast('someEvent', [1,2,3]);
    }

    function secondCtrl($scope){
        $scope.$on('someEvent', function(event, mass) {console.log(mass)});
    }

    如果您的作用域之间没有父子关系,您可以将$rootscope插入控制器,并将事件广播到所有子作用域(也就是secondctrl)。

    1
    2
    3
    function firstCtrl($rootScope){
        $rootScope.$broadcast('someEvent', [1,2,3]);
    }

    最后,当需要将事件从子控制器分派到向上的作用域时,可以使用$scope.$emit。如果firstctrl的作用域是secondctrl作用域的父级:

    1
    2
    3
    4
    5
    6
    7
    function firstCtrl($scope){
        $scope.$on('someEvent', function(event, data) { console.log(data); });
    }

    function secondCtrl($scope){
        $scope.$emit('someEvent', [1,2,3]);
    }

    还有两个问题:(非服务方法)

    1)对于父子控制器-使用父控制器的$scope来发出/广播事件。http://jsfiddle.net/laan_sachin/jnj6y/

    2)在非相关控制器上使用$rootScope。http://jsfiddle.net/vxaff/


    实际上,使用emit和broadcast效率很低,因为事件在作用域层次结构中上下冒泡,这很容易降低为复杂应用程序的性能装瓶。

    我建议使用服务。以下是我最近在我的一个项目中实现它的方法-https://gist.github.com/3384419。

    基本思想-将pub子/事件总线注册为服务。然后在需要订阅或发布事件/主题的地方插入该事件总线。


    我不知道这是否超出了标准,但是如果您的所有控制器都在同一个文件中,那么您可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    app = angular.module('dashboardBuzzAdmin', ['ngResource', 'ui.bootstrap']);

    var indicatorsCtrl;
    var perdiosCtrl;
    var finesCtrl;

    app.controller('IndicatorsCtrl', ['$scope', '$http', function ($scope, $http) {
      indicatorsCtrl = this;
      this.updateCharts = function () {
        finesCtrl.updateChart();
        periodsCtrl.updateChart();
      };
    }]);

    app.controller('periodsCtrl', ['$scope', '$http', function ($scope, $http) {
      periodsCtrl = this;
      this.updateChart = function() {...}
    }]);

    app.controller('FinesCtrl', ['$scope', '$http', function ($scope, $http) {
      finesCtrl = this;
      this.updateChart = function() {...}
    }]);

    如您所见,指示符sctrl在调用updateCharts时调用其他两个控制器的updateChart函数。


    我也知道这一点。

    1
    angular.element($('#__userProfile')).scope().close();

    但我不太喜欢使用它,因为我不喜欢在角度代码中使用jquery选择器。


    可以在父控制器(messagectrl)中插入"$controller"服务,然后使用以下方法实例化/插入子控制器(datecrl):$scope.childController = $controller('childController', { $scope: $scope.$new() });

    现在,您可以通过调用子控制器的方法来访问子控制器中的数据,因为它是一个服务。如果有任何问题,请告诉我。


    有一种方法不依赖于服务,即$broadcast$emit。它并不适用于所有情况,但是如果您有两个相关的控制器可以抽象为指令,那么您可以在指令定义中使用require选项。这很可能是NGModel和NGForm的通信方式。您可以使用它在嵌套的指令控制器之间或在同一元素上进行通信。

    对于父/子情况,使用如下:

    1
     

    使它工作的要点是:在parent指令中,使用要调用的方法,应该在this上定义它们(而不是在$scope上定义它们):

    1
    2
    3
    4
    5
    controller: function($scope) {
      this.publicMethodOnParentDirective = function() {
        // Do something
      }
    }

    在子指令定义上,您可以使用require选项,以便将父控制器传递给链接函数(这样您就可以从子指令的scope调用对它的函数)。

    1
    2
    3
    4
    5
    6
    7
    require: '^parentDirective',
    template: '<span ng-click="onClick()">Click on this to call parent directive</span>',
    link: function link(scope, iElement, iAttrs, parentController) {
      scope.onClick = function() {
        parentController.publicMethodOnParentDirective();
      }
    }

    以上内容请访问http://plnkr.co/edit/poeq460vmqer8gl9w8oz?P=预览

    类似地使用了兄弟指令,但同一元素上的两个指令都是:

    1
     

    用于在directive1上创建方法:

    1
    2
    3
    4
    5
    controller: function($scope) {
      this.publicMethod = function() {
        // Do something
      }
    }

    在Directive2中,可以使用require选项来调用它,这会导致将siblingcontroller传递给link函数:

    1
    2
    3
    4
    5
    6
    7
    require: 'directive1',
    template: '<span ng-click="onClick()">Click on this to call sibling directive1</span>',
    link: function link(scope, iElement, iAttrs, siblingController) {
      scope.onClick = function() {
        siblingController.publicMethod();
      }
    }

    这可以在http://plnkr.co/edit/mud2snf9zvadfndxq85w上看到?P =预览。

    这个的用途?

    • 父元素:子元素需要向父元素"注册"自己的任何情况。很像NGModel和NGForm之间的关系。这些可以添加一些可能影响模型的行为。您可能也有一些纯粹基于DOM的东西,其中父元素需要管理某些子元素的位置,比如管理滚动或对滚动做出反应。

    • 同级:允许指令修改其行为。ngmodel是一个经典的例子,用于向输入上使用的ngmodel添加解析器/验证。


    在角度1.5中,可以通过执行以下操作来实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    (function() {
      'use strict';

      angular
        .module('app')
        .component('parentComponent',{
          bindings: {},
          templateUrl: '/templates/products/product.html',
          controller: 'ProductCtrl as vm'
        });

      angular
        .module('app')
        .controller('ProductCtrl', ProductCtrl);

      function ProductCtrl() {
        var vm = this;
        vm.openAccordion = false;

        // Capture stuff from each of the product forms
        vm.productForms = [{}];

        vm.addNewForm = function() {
          vm.productForms.push({});
        }
      }

    }());

    这是父组件。在这里,我创建了一个函数,它将另一个对象推送到我的productForms数组中——注意——这只是我的例子,这个函数实际上可以是任何东西。

    现在我们可以创建另一个组件来使用require

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    (function() {
      'use strict';

      angular
        .module('app')
        .component('childComponent', {
          bindings: {},
          require: {
            parent: '^parentComponent'
          },
          templateUrl: '/templates/products/product-form.html',
          controller: 'ProductFormCtrl as vm'
        });

      angular
        .module('app')
        .controller('ProductFormCtrl', ProductFormCtrl);

      function ProductFormCtrl() {
        var vm = this;

        // Initialization - make use of the parent controllers function
        vm.$onInit = function() {
          vm.addNewForm = vm.parent.addNewForm;
        };  
      }

    }());

    在这里,子组件正在创建对父组件函数addNewForm的引用,然后可以将其绑定到HTML并像其他任何函数一样调用。


    下面是一个与角度js无关的publish-subscribe方法。

    搜索参数控制器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //Note: Multiple entities publish the same event
    regionButtonClicked: function ()
    {
            EM.fireEvent('onSearchParamSelectedEvent', 'region');
    },

    plantButtonClicked: function ()
    {
            EM.fireEvent('onSearchParamSelectedEvent', 'plant');
    },

    Search Choices Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //Note: It subscribes for the 'onSearchParamSelectedEvent' published by the Search Param Controller
    localSubscribe: function () {
            EM.on('onSearchParamSelectedEvent', this.loadChoicesView, this);

    });


    loadChoicesView: function (e) {

            //Get the entity name from eData attribute which was set in the event manager
            var entity = $(e.target).attr('eData');

            console.log(entity);

            currentSelectedEntity = entity;
            if (entity == 'region') {
                $('.getvalue').hide();
                this.loadRegionsView();
                this.collapseEntities();
            }
            else if (entity == 'plant') {
                $('.getvalue').hide();
                this.loadPlantsView();
                this.collapseEntities();
            }


    });

    事件管理器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    myBase.EventManager = {

        eventArray:new Array(),


        on: function(event, handler, exchangeId) {
            var idArray;
            if (this.eventArray[event] == null) {
                idArray = new Array();
            } else {
                idArray = this.eventArray[event];
            }
            idArray.push(exchangeId);
            this.eventArray[event] = idArray;

            //Binding using jQuery
            $(exchangeId).bind(event, handler);
        },

        un: function(event, handler, exchangeId) {

            if (this.eventArray[event] != null) {
                var idArray = this.eventArray[event];
                idArray.pop(exchangeId);
                this.eventArray[event] = idArray;

                $(exchangeId).unbind(event, handler);
            }
        },

        fireEvent: function(event, info) {
            var ids = this.eventArray[event];

            for (idindex = 0; idindex < ids.length; idindex++) {
                if (ids[idindex]) {

                    //Add attribute eData
                    $(ids[idindex]).attr('eData', info);
                    $(ids[idindex]).trigger(event);
                }
            }
        }
    };

    全球的

    1
    var EM = myBase.EventManager;