2012-02-15 159 views
537

是否有可能让一个控制器使用另一个控制器?一个控制器可以调用另一个控制器吗?

例如:

该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和访问服务有关。

+4

这谷歌组螺纹,https://groups.google.com/d/topic/angular/m_mn-8gnNt4/discussion,讨论了5种方式控制器能够彼此交谈。 –

+0

有很好的答案已经在这里,所以我只是想指出的是,对于特定用例所提到的,也许是一个AngularJS过滤器会是一个更好的解决办法?只是以为我会提到它:) –

回答

670

如何在控制器之间进行通信有多种方式。

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

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

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

+50

什么?为什么?所有控制器都由Angular的DI注入。 – Vojta

+4

@Vojta一个控制器如何得知另一个控制器更新了服务?服务是否可以访问范围?服务是否触发了控制器收听的事件?这些问题是我在查看这个答案时尝试实现的。但是,嘿,只有我,貌似对大多数人来说,你的答案1是非常有益的:) –

117

看到这个小提琴: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']; 
+12

上面的小提琴和视频共享一项服务。 这是一个使用$ scope的小提琴$ emit:http://jsfiddle.net/VxafF/ –

+1

@adardesign:我很想读懂指令中的简洁而有意义的例子(谢谢你的回答!) – sscarduzio

+0

很好的答案,我使用myModule.service('mySharedService',函数($ rootScope){})而不是myModule.factory,但它可以起作用! – TacoEater

16

实际使用EMIT和广播是低效的,因为该事件冒起来,做范围层次结构可轻易降级为复杂应用程序的性能瓶装。

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

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

+2

听起来很有希望,但链接没有工作 –

51

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

<!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

+0

如果'theService'更新'thing.x',那么这个更改会自动支持到'FirstCtrl'和'SecondCtrl'中的,对不对?也可以直接通过两个(右?)中的任何一个来更改'thing.x'。 – KajMagnus

+4

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

+0

我以前的评论中的链接是404,所以这里是服务指南,今天,注释服务是单例:https:// docs。 angularjs.org/guide/services – exclsr

23

两个以上小提琴:(非服务方式)

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

2)跨非相关控制器使用$rootScopehttp://jsfiddle.net/VxafF/

+0

事件的复杂性是什么原因?为什么不做这样的事情? http://jsfiddle.net/jnj6y/32/ – Dfr

+0

这取决于什么样的父母子女关系的权利。它可能是一个DOM拓扑,它可以让事件分离。 – DarkKnight

3

我也知道这种方式。

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

但我不使用太多,因为我不喜欢在角代码中使用jQuery选择。

+0

最佳答案。这么简单和容易... =) – zVictor

+2

@zVictor,这真是一种“最后的手段”类型的方法。它可以工作,但它突破了范围,以便强制回到原来的位置。这是使用DOM操作来强制执行某些操作,而不是通过编程来完成。它很简单,它可以工作,但不可扩展。 –

+0

@BrianNoah,真实。可以将这段代码用于原型或某些实验,但不适用于生产代码。 –

2

有一个方法,不依赖于服务,$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的子指令。

以上可以在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使用添加解析器/验证。

1

以下是不考虑Angular JS的publish-subscribe方法。

搜索帕拉姆控制器

//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; 
29

如果您正在寻找发出&广播活动,分享数据,或致电跨控制器功能,请看看这个link:和zbynour(答案有最多投票数)检查答案。我引用他的答案!

如果firstCtrl的范围是secondCtrl范围的父母,你的代码应该以替换$由$在firstCtrl广播发射工作:

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

最后,当你需要派遣从子控制器的情况下向上作用域可以使用$范围。$放出。如果firstCtrl的范围是secondCtrl范围的父:

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

function secondCtrl($scope){ 
    $scope.$emit('someEvent', [1,2,3]); 
} 
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() {...} 
}]); 

正如你可以看到调用updateCharts时indicatorsCtrl呼吁其他两个控制器的updateChart funcions。

2

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

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

45

如果你想调用一个控制器到另一个有可用

  1. $ rootScope。$放出()和$ rootScope四种方法。$ broadcast()
  2. 如果第二个控制器是小孩,则可以使用父级子通信。
  3. 使用服务
  4. 类黑客攻击 - 与angular.element的帮助()

1 $ rootScope $发出()和$ rootScope $广播()

控制器及其作用域可能被破坏 但$ rootScope保留在应用程序中,这就是为什么我们要使用$ rootScope,因为$ rootScope是所有作用域的父级。

如果您是从父执行通信的孩子,甚至孩子想与它的兄弟姐妹沟通,你可以使用$广播

如果从孩子进行到家长的沟通,没有invovled兄弟姐妹,那么你可以使用$ rootScope。$发出

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.如果第二个控制器是孩子,你可以使用儿童家长的沟通

它的最佳方法之一,如果你想做到哪里的孩子要传达孩子家长的沟通直接父母那么它不会需要任何种类的$广播或$发出,但如果你想做从父母到孩子的沟通,那么你必须使用服务或$广播

例如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的依赖注入mecahnism注入维护和测试。服务。

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

它会给输出你好儿童世界和家长您好世界。根据服务的角度文档单身 - 依赖于服务的每个组件获得由服务工厂产生的单一实例的引用。

黑客4.Kind - 与angular.element的帮助()

该方法通过ID /唯一class.angular.element()方法返回获取从元素范围()元素和作用域()给另一个变量的$ 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的自身的价值,当你将点击的文本,你会在终端也随之得到的值。如果您单击父控制器跨度,浏览器将控制孩子的值,反之亦然。

1

在角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,并呼吁像任何一个参考其他功能。

相关问题