一个AngularJS控制器可以调用另一个吗?


581

一个控制器可以使用另一个控制器吗?

例如:

该HTML文档仅MessageCtrlmessageCtrl.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服务有关。


4
这个google群组线程groups.google.com/d/topic/angular/m_mn-8gnNt4/discussion讨论了控制器可以相互交谈的5种方式。
Mark Rajcok 2012年

这里已经有了很好的答案,所以我想指出,对于提到的特定用例,也许AngularJS过滤器会是更好的解决方案?只是以为我会提到它:)
乔·丹代尔

Answers:


705

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

最好的一种可能是共享服务:

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

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


4
嗨,第一个示例将要求网页知道堆栈中的所有服务。感觉像难闻的气味(?)。与第二个一样,网页是否不需要提供$ scope参数?
BanksySan

54
什么?为什么?所有控制器均由Angular的DI注入。
Vojta 2012年

7
在1 /中的@JoshNoe中,您有两个控制器(或多个),并且它们都获得一个相同/共享的服务。然后,您有多种交流方式,您提到了其中的一些方式。我会根据您的特定用例来决定。您可以将共享逻辑/状态放入服务中,并且两个控制器仅委派给该服务,甚至将服务导出到模板。当然,该服务还可以
引发

137
谈到这么晚:你们知道您正在与使用AngularJS的Google的The Vojta争论,对吗?:)
Suman 2014年

16
对我而言,显而易见的是,在我的HTML中,发出事件的控制器必须是侦听控制器的子节点才能起作用。
djangonaut

122

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

另请观看以下视频:控制器之间的通信

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

12
以上小提琴和视频共享服务。这是一个使用$ scope。$ emit的小提琴:jsfiddle.net/VxafF
Mark Rajcok 2012年

1
@adardesign:我希望阅读同样简洁明了的指令示例(也感谢您的回答!)
sscarduzio

很好的答案,我使用myModule.service('mySharedService',function($ rootScope){})代替myModule.factory,但它仍然有效!
TacoEater

优秀的。但是,我有一个问题:为什么要在ControllerZero中添加处理程序?$ scope。$ on('handleBroadcast',function(){$ scope.message = sharedService.message;});
ZooZ

提供的视频真棒!我似乎这是我需要从另一个控制器查询另一个控制器的状态的东西。但是,使用“调用”功能不起作用。它使用“触发”动作工作。如此有效地,如果一个控制器执行一个动作并具有一个新的状态,则它将必须广播该状态,并且由其他控制器来监听该广播并做出相应的响应。或者更好的是,在共享服务中执行操作,然后广播状态。请告诉我我的理解是否正确。
tarekahf

53

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

  1. $ rootScope。$ emit()和$ rootScope。$ broadcast()
  2. 如果Second controller是child,则可以使用Parent child通讯。
  3. 使用服务
  4. 黑客的一种-在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也会在兄弟姐妹内部调用父级。这是性能起作用的地方。如果您正在使用子代与父代的通信,则它是可取的,因为它会跳过一些脏检查。

2.如果“第二个控制器”是孩子,则可以使用“孩子父母”通信

这是最好的方法之一,如果您想在孩子想要与直系父母沟通的地方进行孩子父母沟通,则不需要任何$ 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文档Singletons –依赖于服务的每个组件都将引用由服务工厂生成的单个实例

4,黑客的种类-在angular.element()的帮助下

此方法通过其ID /唯一类从元素获取scope()。angular.element()方法返回元素,并且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>

Angular.js:-

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


52

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

<!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,那么这个变化自动propageates到<input> S IN FirstCtrlSecondCtrl,对不对?一个人也可以thing.x直接通过两个<input>中的任何一个进行更改(对吗?)。
KajMagnus 2012年

4
是。所有Angular服务都是应用程序单例,这意味着只有一个Service实例。参考:docs.angularjs.org/guide/dev_guide.services.creating_services
exclsr 2013年

我之前的评论中的链接是404,因此今天是服务指南,其中指出服务是单例:docs.angularjs.org/guide/services
exclsr 2014年

1
@exclsr是的!抱歉,我之前错过了
CodyBugstein 2014年

3
到目前为止,这是我到目前为止在网上看到的最好的例子。谢谢
Sevenearths

33

如果您希望发出和广播事件以在控制器之间共享数据或调用功能,请查看此链接:并检查答案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]);
}


17

实际上,使用发射和广播效率不高,因为事件在作用域层次结构中上下起泡,对于复杂的应用程序而言,事件层次结构很容易降低性能。

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

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


5

我也知道这种方式。

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

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


最好的答案。如此简单容易... =)
zVictor 2014年

3
@zVictor,这确实是一种“不得已”的方法。它可以工作,但是为了强制您返回而超出了范围。这是使用DOM操作来强制执行某项操作,而不是通过编程方式进行。它很简单,可以工作,但是却不可扩展。
布莱恩·诺亚

2
@BrianNoah,是的。可以将此代码用于原型或某些实验,但不能用于生产代码。
Andrey Korchak 2014年

1
那是最糟糕的事情。服务中的DOM操作和直接作用域访问。
Mattia Franchetto 2015年

3

有一种方法不依赖于服务,$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选项将其调用,从而将siblingController传递给链接函数:

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的东西,其中父元素需要管理某些子元素的位置,例如要管理或响应滚动。

  • 同级:允许指令修改其行为。ngModel是经典情况,向输入的ngModel添加解析器/验证。


3

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

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

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


2

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

现在,您可以通过调用子控制器的方法访问子控制器的数据,因为它是一项服务。
让我知道是否有任何问题。


1

以下是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;

1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.