5

Может ли один контроллер AngularJS вызывать другой?

14

Вопрос: Возможно ли, чтобы один контроллер использовал другой?

Здравствуйте, сообщите, пожалуйста, о решении проблемы. Я работаю с AngularJS и сталкиваюсь с ситуацией, когда нужно использовать данные из одного контроллера в другом.

Вот мой HTML-документ, который просто выводит сообщение, предоставляемое контроллером MessageCtrl из файла messageCtrl.js:

<html xmlns:ng="http://angularjs.org/">
<head>
    <meta charset="utf-8" />
    <title>Взаимодействие между контроллерами</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>

Файл контроллера messageCtrl.js содержит следующий код:

function MessageCtrl() {
    this.message = function() { 
        return "Текущая дата: " + new Date().toString(); 
    };
}

Этот контроллер просто отображает текущую дату.

Теперь я хотел бы добавить еще один контроллер, DateCtrl, который будет передавать дату в определенном формате обратно в MessageCtrl. Как мне это реализовать? У меня возникли трудности с пониманием, как AngularJS управляет зависимостями (DI) и как это связано с взаимодействием между контроллерами, так как в основном документации говорится о XmlHttpRequests и доступе к сервисам.

Есть ли какие-то рекомендации или примеры, как можно организовать взаимодействие между контроллерами в AngularJS?

5 ответ(ов)

7

Существует несколько способов коммуникации между контроллерами в AngularJS.

Один из лучших способов — это использование общего сервиса:

function FirstController(someDataService) 
{
  // используйте сервис данных, привяжите его к шаблону...
  // или вызывайте методы someDataService для отправки запросов на сервер
}

function SecondController(someDataService) 
{
  // имеет ссылку на тот же экземпляр сервиса
  // поэтому, если сервис обновляет состояние, этот контроллер об этом знает
}

Другой способ — это использование событий в скоупе:

function FirstController($scope) 
{
  $scope.$on('someEvent', function(event, args) {});
  // другой контроллер или даже директива
}

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

В обоих случаях вы также можете взаимодействовать с любой директивой.

0

Если вы хотите вызвать один контроллер из другого, есть четыре доступных метода:

  1. $rootScope.$emit() и $rootScope.$broadcast()
  2. Если второй контроллер является дочерним, вы можете использовать коммуникацию родитель-дитя.
  3. Используйте сервисы.
  4. Крайний метод — с помощью angular.element().

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

Контроллер и его область видимости могут быть уничтожены, но $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">
        <!-- Первый контроллер-сосед -->
      </div>
      <div ng-controller="Sibling2" class="ng-scope">
        <!-- Второй контроллер-сосед -->
        <div ng-controller="Child" class="ng-scope">
          <!-- Контроллер-ребенок -->
        </div>
      </div>
    </div>
</body>

AngularJS Код:

var app = angular.module('myApp', []); // Используем его во всем примере 
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. Если второй контроллер — дочерний, используйте коммуникацию родитель-дитя

Это один из лучших методов, если вам нужно сделать коммуникацию ребёнок-родитель. В этом случае для общения ребенка с непосредственным родителем не нужно использовать $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) { // Внедрение зависимостей
    console.log(communicate.communicateValue + " Parent World");
});

app.controller('ChildCtrl', function(communicate) { // Внедрение зависимостей
    console.log(communicate.communicateValue + " Child World");
});

Это даст вывод Hello Child World и Hello Parent World. Согласно документации Angular по сервисам, Синглетоны – каждый компонент, зависимый от сервиса, получает ссылку на единственный экземпляр, созданный фабрикой сервиса.

4. Крайний метод — с помощью angular.element()

Этот метод получает scope() от элемента по его ID или уникальному классу. Метод angular.element() возвращает элемент, а scope() возвращает переменную $scope другого контроллера. Использование $scope одного контроллера внутри другого — не очень хорошая практика.

HTML:

<div id='parent' ng-controller='ParentCtrl'>{{varParent}}
    <span ng-click='getValueFromChild()'>Нажмите, чтобы получить значение от ребенка</span>
    <div id='child' ng-controller='ChildCtrl'>{{varChild}}
        <span ng-click='getValueFromParent()'>Нажмите, чтобы получить значение от родителя</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, и когда вы щелкнете по тексту, вы получите значения в консоли соответственно. Если нажать на span родительского контроллера, в консоли появится значение ребенка, и наоборот.

0

Вопрос: Как сделать так, чтобы два контроллера в AngularJS могли совместно использовать данные из сервиса?

Ответ: Вы можете настроить общие данные с помощью сервиса в AngularJS. Вот пример, где два контроллера обмениваются данными через один сервис:

<!doctype html>
<html ng-app="project">
<head>
    <title>Angular: Пример использования сервиса</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 = "Первый Контроллер";
}

// Второй контроллер, использующий тот же сервис
function SecondCtrl($scope, theService) {   
    $scope.someThing = theService.thing; // Получаем доступ к тем же данным сервиса
    $scope.name = "Второй Контроллер!";
}
    </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>

В этом примере оба контроллера (FirstCtrl и SecondCtrl) используют один и тот же сервис theService, который содержит объект thing с параметром x. Изменения в x в одном контроллере будут автоматически обновляться в другом, так как они ссылаются на один и тот же объект. Это простой способ разделения данных между контроллерами в AngularJS.

Как дополнительный ресурс, вы можете ознакомиться с этим гистом.

0

Для вашего вопроса о том, как можно использовать подход без сервисов в AngularJS для передачи данных между контроллерами (родительский и дочерний), приведу два примера:

  1. Родительский и дочерний контроллер: Можно использовать $scope родительского контроллера для эмитации или широковещательной передачи событий. Это позволяет дочернему контроллеру реагировать на события, сгенерированные родителем. Вот пример, который иллюстрирует это решение: jsfiddle.

  2. Использование $rootScope: Если контроллеры не связаны друг с другом и вам нужно передавать данные между ними, вы можете использовать $rootScope. Это глобальная область видимости, которая позволяет вам делиться данными и событиями между несвязанными контроллерами. Посмотрите этот пример для лучшего понимания: jsfiddle.

Эти примеры показывают, как передавать данные между контроллерами без создания сервисов. Однако, обратите внимание, что использование $rootScope и эмита событий в больших приложениях может привести к недостаточной читаемости и трудностям в поддержке, поэтому стоит рассмотреть использование сервисов для более структурированного подхода.

0

Использование методов emit и broadcast действительно может быть неэффективным, поскольку события поднимаются и опускаются по иерархии скоупов, что может привести к проблемам с производительностью в сложных приложениях.

Я бы рекомендовал использовать сервис. Вот как я недавно реализовал это в одном из своих проектов - ссылка на Gist.

Основная идея заключается в регистрации системы публикации-подписки (pub-sub) или шины событий (event bus) в качестве сервиса. Затем вы можете инжектировать этот event bus везде, где вам нужно подписываться на события или публиковать их.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь