2012-02-15 154 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,並呼籲像任何一個參考其他功能。

相關問題