【问题标题】:Can one AngularJS controller call another?一个 AngularJS 控制器可以调用另一个吗?
【发布时间】:2012-03-06 19:07:45
【问题描述】:

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

例如:

此 HTML 文档仅打印 MessageCtrl 控制器在 messageCtrl.js 文件中传递的消息。

<html xmlns:ng="http://angularjs.org/">
<head>
    <meta charset="utf-8" />
    <title>Inter Controller Communication</title>
</head>
<body>
    <div ng:controller="MessageCtrl">
        <p>{{message}}</p>
    </div>

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

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

function MessageCtrl()
{
    this.message = function() { 
        return "The current date is: " + new Date().toString(); 
    };
}

仅打印当前日期;

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

【问题讨论】:

  • 这个 google 群组线程 groups.google.com/d/topic/angular/m_mn-8gnNt4/discussion 讨论了控制器可以相互通信的 5 种方式。
  • 这里已经有了很好的答案,所以我只想指出,对于提到的特定用例,也许 AngularJS 过滤器会是更好的解决方案?只是想我会提到它:)

标签: javascript html angularjs


【解决方案1】:

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

最好的可能是共享服务:

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
}

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

function FirstController($scope) 
{
  $scope.$on('someEvent', function(event, args) {});
  // another controller or even directive
}

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

在这两种情况下,您也可以使用任何指令进行通信。

【讨论】:

  • 嗨,第一个示例需要网页了解堆栈中的所有服务。感觉像难闻的气味(?)。和第二个一样,网页不需要提供 $scope 参数吗?
  • 什么?为什么?所有控制器都由 Angular 的 DI 注入。
  • @JoshNoe in 1/ 你有两个(或更多)控制器,它们都得到一个相同/共享的服务。然后,您有多种沟通方式,其中一些是您提到的。我会根据您的具体用例来决定。您可以将共享逻辑/状态放入服务中,并且两个控制器仅委托给该服务,甚至将服务导出到模板。当然,服务也可以触发事件……
  • 这么晚了:你们知道你们在和谷歌的 AngularJS 工作的 Vojta 争论,对吧? :)
  • 在我的 HTML 中,事件发射控制器必须是监听控制器的子节点才能工作,这对我来说并不明显。
【解决方案2】:

看到这个小提琴:http://jsfiddle.net/simpulton/XqDxG/

另请观看以下视频:Communicating Between Controllers

HTML:

<div ng-controller="ControllerZero">
  <input ng-model="message" >
  <button ng-click="handleClick(message);">LOG</button>
</div>

<div ng-controller="ControllerOne">
  <input ng-model="message" >
</div>

<div ng-controller="ControllerTwo">
  <input ng-model="message" >
</div>

javascript:

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'];

【讨论】:

  • 上述小提琴和视频共享服务。这是一个使用 $scope.$emit 的小提琴:jsfiddle.net/VxafF
  • @adardesign:我很想阅读同样简洁而有意义的指令示例(也感谢这个答案!)
  • 很好的答案,我使用 myModule.service('mySharedService', function($rootScope) {}) 而不是 myModule.factory 但它仍然有效!
  • 优秀。不过,我有一个问题:为什么要在 ControllerZero 中添加处理程序? $scope.$on('handleBroadcast', function() { $scope.message = sharedService.message; });
  • 提供的视频真的很棒!我似乎这是我需要从另一个控制器查询另一个控制器的状态。但是,这不适用于“调用”功能。它使用“触发”动作工作。如此有效,如果一个控制器执行了一个动作,并且有一个新的状态,那么,它就必须广播这个状态,而其他控制器可以监听那个广播并做出相应的响应。或者更好的是,在共享服务中执行操作,然后广播状态。请告诉我我的理解是否正确。
【解决方案3】:

实际上使用发射和广播是低效的,因为事件在范围层次结构中上下冒泡,这很容易降低复杂应用程序的性能瓶颈。

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

基本理念 - 将 pub-sub/event bus 注册为服务。然后在您需要订阅或发布事件/主题的任何地方注入该事件总线。

【讨论】:

    【解决方案4】:

    这里是两个控制器共享服务数据的一页示例:

    <!doctype html>
    <html ng-app="project">
    <head>
        <title>Angular: Service example</title>
        <script src="http://code.angularjs.org/angular-1.0.1.js"></script>
        <script>
    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!";
    }
        </script>
    </head>
    <body>  
        <div ng-controller="FirstCtrl">
            <h2>{{name}}</h2>
            <input ng-model="thing.x"/>         
        </div>
    
        <div ng-controller="SecondCtrl">
            <h2>{{name}}</h2>
            <input ng-model="someThing.x"/>             
        </div>
    </body>
    </html>
    

    也在这里:https://gist.github.com/3595424

    【讨论】:

    • 如果theService 更新thing.x,那么这种变化会自动传播到FirstCtrlSecondCtrl 中的,对吗?也可以通过两个 中的任何一个直接更改thing.x(对吗?)。
    • 是的。所有 Angular 服务都是应用程序单例,这意味着该服务只有一个实例。参考:docs.angularjs.org/guide/dev_guide.services.creating_services
    • 我之前评论中的链接是 404,所以这里是今天的服务指南,说明服务是单例的:docs.angularjs.org/guide/services
    • @exclsr 是的!对不起,我之前错过了
    • 迄今为止我在网上看到的最好的例子。谢谢
    【解决方案5】:

    另外两个小提琴:(非服务方法)

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

    2) 在不相关的控制器中使用$rootScopehttp://jsfiddle.net/VxafF/

    【讨论】:

    • 事件如此复杂的原因是什么?为什么不做这样的事情? jsfiddle.net/jnj6y/32
    • 这取决于什么样的父子关系正确。这可能是一个 DOM 层次结构,案例事件允许您解耦。
    【解决方案6】:

    我也知道这种方式。

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

    但我并没有用太多,因为我不喜欢在 Angular 代码中使用 jQuery 选择器。

    【讨论】:

    • 最佳答案。如此简单易行... = )
    • @zVictor,这确实是一种“最后的手段”类型的方法。它有效,但它超出了范围以强制您重新进入。这是使用 DOM 操作来强制完成某些事情,而不是以编程方式执行它。它很简单,它有效,但它不可扩展。
    • @BrianNoah,真的。可以将此代码用于原型或一些实验,但不能用于生产代码。
    • 这是最糟糕的。服务中的 DOM 操作和直接范围访问。
    【解决方案7】:

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

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

    <div parent-directive>
      <div inner-directive></div>
    </div>
    

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

    controller: function($scope) {
      this.publicMethodOnParentDirective = function() {
        // Do something
      }
    }
    

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

    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=preview

    类似地使用同级指令,但两个指令都在同一个元素上:

    <div directive1 directive2>
    </div>
    

    用于在directive1上创建方法:

    controller: function($scope) {
      this.publicMethod = function() {
        // Do something
      }
    }
    

    在指令 2 中,这可以通过使用 require 选项来调用,这会导致将兄弟控制器传递给链接函数:

    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=preview 看到。

    这个有什么用?

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

    • Sibling:允许指令修改其行为。 ngModel 是经典案例,将解析器/验证添加到 ngModel 在输入上的使用。

    【讨论】:

      【解决方案8】:

      以下是publish-subscribe 方法,与 Angular JS 无关。

      搜索参数控制器

      //Note: Multiple entities publish the same event
      regionButtonClicked: function () 
      {
              EM.fireEvent('onSearchParamSelectedEvent', 'region');
      },
      
      plantButtonClicked: function () 
      {
              EM.fireEvent('onSearchParamSelectedEvent', 'plant');
      },
      

      搜索选择控制器

      //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();
              }
      
      
      });
      

      活动经理

      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);
                  }
              }
          }
      };
      

      全球

      var EM = myBase.EventManager;
      

      【讨论】:

        【解决方案9】:

        如果您希望发送和广播事件以在控制器之间共享数据或调用函数,请查看此link:并检查zbynour 的答案(以最高票数回答) .我引用他的回答!!!

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

        function firstCtrl($scope){
            $scope.$broadcast('someEvent', [1,2,3]);
        }
        
        function secondCtrl($scope){
            $scope.$on('someEvent', function(event, mass) {console.log(mass)});
        }
        

        如果您的范围之间没有父子关系,您可以将 $rootScope 注入控制器并将事件广播到所有子范围(即 secondCtrl)。

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

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

        function firstCtrl($scope){
            $scope.$on('someEvent', function(event, data) { console.log(data); });
        }
        
        function secondCtrl($scope){
            $scope.$emit('someEvent', [1,2,3]);
        }
        

        【讨论】:

          【解决方案10】:

          我不知道这是否超出标准,但如果您将所有控制器都放在同一个文件中,那么您可以执行以下操作:

          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() {...}
          }]);
          

          如您所见,indicatorsCtrl 在调用 updateCharts 时正在调用其他两个控制器的 updateChart 函数。

          【讨论】:

            【解决方案11】:

            您可以在父控制器 (MessageCtrl) 中注入“$controller”服务,然后使用以下方法实例化/注入子控制器 (DateCtrl):
            $scope.childController = $controller('childController', { $scope: $scope.$new() });

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

            【讨论】:

              【解决方案12】:

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

              1. $rootScope.$emit() 和 $rootScope.$broadcast()
              2. 如果第二个控制器是子控制器,您可以使用父子通信。
              3. 使用服务
              4. 一种 hack - 在 angular.element() 的帮助下

              1. $rootScope.$emit() 和 $rootScope.$broadcast()

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

              如果你正在执行从父母到孩子的通信,甚至孩子想和它的兄弟姐妹交流,你可以使用 $broadcast

              如果您正在执行从子到父的通信,没有涉及兄弟姐妹,那么您可以使用 $rootScope.$emit

              HTML

              <body ng-app="myApp">
                  <div ng-controller="ParentCtrl" class="ng-scope">
                    // ParentCtrl
                    <div ng-controller="Sibling1" class="ng-scope">
                      // Sibling first controller
                    </div>
                    <div ng-controller="Sibling2" class="ng-scope">
                      // Sibling Second controller
                      <div ng-controller="Child" class="ng-scope">
                        // Child controller
                      </div>
                    </div>
                  </div>
              </body>
              

              Angularjs 代码

               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。如果 Second controller 是 child,则可以使用 Child Parent 通信

              这是最好的方法之一,如果你想做 child parent communication 孩子想要与 immediate parent 交流,那么它不需要任何类型的 $broadcast 或$emit 但是如果你想从父母到孩子进行交流,那么你必须使用服务或 $broadcast

              例如 HTML:-

              <div ng-controller="ParentCtrl">
               <div ng-controller="ChildCtrl">
               </div>
              </div>
              

              Angularjs

               app.controller('ParentCtrl', function($scope) {
                 $scope.value='Its parent';
                    });
                app.controller('ChildCtrl', function($scope) {
                 console.log($scope.value);
                });
              

              每当您使用子与父通信时,Angularjs 将在子内部搜索变量,如果内部不存在,则它将选择查看父控制器内部的值。

              3.使用服务

              AngularJS 使用服务架构支持“关注点分离”的概念。服务是javascript函数,只负责执行特定任务。这使它们成为可维护和可测试的可维护和可测试个体实体。用于使用Angularjs的依赖注入机制注入的服务.

              Angularjs 代码:

              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 。根据 Angular 服务文档 Singleton – 依赖于服务的每个组件都会获得对服务工厂生成的单个实例的引用

              4. 一种 hack - 在 angular.element() 的帮助下

              此方法通过其 Id/唯一的 class.angular.element() 方法从元素获取 scope() 方法返回元素,并且 scope() 使用另一个控制器中的一个控制器的 $scope 变量给出另一个变量的 $scope 变量不是很好的做法。

              HTML:-

              <div id='parent' ng-controller='ParentCtrl'>{{varParent}}
               <span ng-click='getValueFromChild()'>Click to get ValueFormChild</span>
               <div id='child' ng-controller='childCtrl'>{{varChild}}
                 <span ng-click='getValueFromParent()'>Click to get ValueFormParent </span>
               </div>
              </div>
              

              Angularjs:-

              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 上显示它们自己的值,当您单击文本时,您将在控制台中相应地获取值。如果您单击父控制器跨度,浏览器将控制子控制器的值,反之亦然。

              【讨论】:

                【解决方案13】:

                在 Angular 1.5 中,这可以通过执行以下操作来完成:

                (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的组件:

                (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 并像任何其他函数一样调用。

                【讨论】:

                  【解决方案14】:

                  您可以使用AngularJS提供的$controller服务。

                  angular.module('app',[]).controller('DateCtrl', ['$scope', function($scope){
                    $scope.currentDate = function(){
                      return "The current date is: " + new Date().toString(); 
                    }
                  }]);
                  
                  angular.module('app').controller('MessageCtrl', ['$scope', function($scope){
                  
                    angular.extend(this, $controller('DateCtrl', {
                        $scope: $scope
                    }));
                  
                    $scope.messageWithDate = function(message){
                      return "'"+ message + "', " + $scope.currentDate;
                    }
                  
                    $scope.action2 = function(){
                      console.log('Overridden in ChildCtrl action2');
                    }
                  
                  }]);
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-09-30
                    • 1970-01-01
                    • 2015-09-27
                    • 2013-08-29
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多