Чи може один контролер AngularJS викликати іншого?


581

Чи можливо один контролер використовувати інший?

Наприклад:

Цей 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
Цей потік групи Google, groups.google.com/d/topic/angular/m_mn-8gnNt4/discussion , обговорює 5 способів, як контролери можуть спілкуватися один з одним.
Марк Райкок

Тут вже є хороші відповіді, тому я просто хотів би зазначити, що для конкретного згаданого випадку використання, можливо, фільтр AngularJS був би кращим рішенням? Тільки думав, що це згадаю :)
Джо Діндал

Відповіді:


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
Hia, Перший приклад вимагає, щоб веб-сторінка була обізнана про всі сервіси в стеку. Що відчуває поганий запах (?). Як і у другій, чи не потрібно веб-сторінці наводити аргумент $ range?
BanksySan

54
Що? Чому? Усі контролери вводяться DI у Angular.
Войта

7
@JoshNoe в 1 / у вас є два контролери (або більше), і вони обидва отримують одну ідентичну / спільну послугу. Потім у вас є кілька способів спілкування, деякі з них ви згадали. Я б вирішив, виходячи з Вашого конкретного випадку використання. Ви можете помістити спільну логіку / стан у службу, і обидва контролери лише делегують їй цю службу або навіть експортують її до шаблону. Звичайно, служба може також розстрілювати події ...
Войта

137
До цього пізно: ви, хлопці, знаєте, що ви сперечаєтесь з THE Vojta від Google, який працює над AngularJS, правда? :)
Суман

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
Наведені вище скрипки та відео поділяють послугу. Ось загадка,
Марк Райчко

1
@adardesign: Я хотів би прочитати той самий стислий і змістовний приклад для директив (дякую також за цю відповідь!)
sscarduzio

Чудовий відповідь, я використовую myModule.service ('mySharedService', функція ($ rootScope) {}) замість myModule.factory, але він все одно працює!
TacoEater

Відмінно. Однак у мене виникає питання: Чому ви додали обробник в ControllerZero? $ range. $ on ('handleBroadcast', функція () {$ range.message = sharedService.message;});
ZooZ

Надане відео дійсно приголомшливе! Мені здається, це те, що мені потрібно запитати про стан іншого контролера у іншого контролера. Однак це не працює, використовуючи функцію "виклик". Він працює за допомогою дії "тригер". Тож ефективно, якщо контролер здійснює дію та має новий стан, тоді йому доведеться транслювати стан, а на інших контролерах слід слухати цю трансляцію та відповідати відповідно. Або ще краще, виконайте дію в спільній службі, а потім транслюйте стан. Скажіть, будь ласка, чи правильно я розумію.
tarekahf

53

Якщо ви хочете зателефонувати одному контролеру в інший, є чотири способи

  1. $ rootScope. $ emit () і $ rootScope. $ Broadcast ()
  2. Якщо другий контролер є дочірнім, ви можете використовувати спілкування з дитиною батьків.
  3. Користуйтеся послугами
  4. Вид хаку - за допомогою angular.element ()

1. $ rootScope. $ Emit () і $ rootScope. $ Broadcast ()

Контролер і його сфера можуть бути знищені, але $ rootScope залишається в додатку, тому ми беремо $ rootScope, оскільки $ rootScope є батьківським для всіх областей.

Якщо ви здійснюєте спілкування від батька до дитини і навіть дитина хоче спілкуватися зі своїми побратимами, ви можете скористатись $ мовленням

Якщо ви здійснюєте спілкування від дитини до батька, братів та сестер не призначається, ви можете використовувати $ 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 is бажано, якщо ви використовуєте дитину для батьківського спілкування, оскільки це пропускає деякі брудні чеки.

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.

Код 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. Згідно з кутовими документами послуг Singletons - кожен компонент, що залежить від послуги, отримує посилання на єдиний екземпляр, створений фабрикою послуг .

4. Вид хак - за допомогою angular.element ()

Цей метод отримує область () від елемента за допомогою його Id / унікального методу class.angular.element (), що повертає елемент, а область () дає $ змінну області іншої змінної, використовуючи змінну $ range одного контролера всередині іншого, не є хорошою практикою.

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, і коли ви натиснете на текст, ви отримаєте значення відповідно в консолі. Якщо ви натиснете на батьківський контролер, то браузер буде консолірувати значення дочірнього і віверса.


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, то ця зміна автоматично відображається на <input> s у, FirstCtrlі SecondCtrlтак? І ви також можете змінитись thing.xбезпосередньо через будь-який з двох <input> s (так?).
KajMagnus

4
Так. Усі кутові сервіси є одноплемінними програмами, а значить, є лише один екземпляр Сервісу. Довідка: docs.angularjs.org/guide/dev_guide.services.creating_services
excelsr

Посилання в моєму попередньому коментарі - 404, тож ось посібник із служб на сьогодні, що служби приміток є одинаковими
excelsr

1
@exclsr Так! Вибачте, що я пропустив це раніше
CodyBugstein

3
На сьогодні найкращий приклад, який я бачив в Інтернеті до цих пір. Дякую
Sevenearths

33

Якщо ви хочете випромінювати та транслювати події для обміну даними чи функціями виклику через контролери , перегляньте це посилання : і перевірте відповідь zbynour(відповідь максимум голосів). Я цитую його відповідь !!!

Якщо область firstCtrl є батьківською областю secondCtrl, ваш код повинен працювати, замінивши $ emit на $ Broadcast у 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]);
}

Нарешті, коли вам потрібно передати подію від дочірнього контролера до масштабів вгору, ви можете використовувати $ range. $ Emit. Якщо область застосування firstCtrl є батьківською областю secondCtrl:

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

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

24

Ще дві загадки: (Підхід без обслуговування)

1) Для контролера $scopeбатько - дитина - використання батьківського контролера для передачі / трансляції подій. http://jsfiddle.net/laan_sachin/jnj6y/

2) Використання $rootScopeміж не пов'язаними контролерами. http://jsfiddle.net/VxafF/


Яка причина всієї цієї складності з подіями? Чому б не зробити щось подібне? jsfiddle.net/jnj6y/32
Dfr

Це залежить від того, які саме стосунки з батьківською дитиною. Це може бути спадщина DOM, тому що події випадку дозволять вам роз'єднати речі.
DarkKnight

17

Насправді використання випромінювання та трансляції неефективне, оскільки подія перетворює вгору та вниз ієрархію сфери, яка може легко погіршитись у розширення продуктивності для складного застосування.

Я б запропонував скористатися послугою. Ось як я нещодавно реалізував це в одному зі своїх проектів - https://gist.github.com/3384419 .

Основна ідея - зареєструвати шину pub-sub / event як послугу. Потім введіть цю шину подій там, де вам потрібно підписатися або опублікувати події / теми.


5

Я також знаю про цей спосіб.

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

Але я його не надто використовую, тому що мені не подобається використовувати селектори jQuery у кутовому коді.


найкраща відповідь. Так просто і легко ... =)
zVictor

3
@zVictor, це дійсно "останній варіант" підходу. Це працює, але він виходить з межі, щоб змусити вас повернутися назад. Для цього використовується маніпуляція DOM, щоб змусити щось зробити, а не робити це програмно. Це просто, це працює, але це не масштабується.
Брайан Ной

2
@BrianNoah, правда. Добре використовувати цей код для прототипів або деяких експериментів, але не для виробничого коду.
Андрій Корчак

1
Це найгірше, що можна зробити. DOM маніпулювання послугами та прямий доступ до сфери.
Маттіа Франшето

3

Існує метод, не залежний від послуг, $broadcastабо $emit. Це не підходить у всіх випадках, але якщо у вас є два пов'язаних контролера, які можна абстрагувати директивами, то ви можете використовувати 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 викликає функції updateChart інших обох контролерів під час виклику updateCharts.


2

Ви можете ввести послугу "контролер $" у своєму батьківському контролері (MessageCtrl), а потім інстанціювати / вводити дочірній контролер (DateCtrl), використовуючи:
$scope.childController = $controller('childController', { $scope: $scope.$new() });

Тепер ви можете отримати доступ до даних свого дитячого контролера, зателефонувавши до його методів, оскільки це послуга.
Повідомте мене, якщо є якесь питання.


1

Далі йде publish-subscribeпідхід, незалежний від кутового JS.

Пошук Param Controller

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

У куті 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 і викликати, як і будь-яку іншу функцію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.