Может ли один контроллер AngularJS вызывать другой?
Вопрос: Возможно ли, чтобы один контроллер использовал другой?
Здравствуйте, сообщите, пожалуйста, о решении проблемы. Я работаю с 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 ответ(ов)
Существует несколько способов коммуникации между контроллерами в AngularJS.
Один из лучших способов — это использование общего сервиса:
function FirstController(someDataService)
{
// используйте сервис данных, привяжите его к шаблону...
// или вызывайте методы someDataService для отправки запросов на сервер
}
function SecondController(someDataService)
{
// имеет ссылку на тот же экземпляр сервиса
// поэтому, если сервис обновляет состояние, этот контроллер об этом знает
}
Другой способ — это использование событий в скоупе:
function FirstController($scope)
{
$scope.$on('someEvent', function(event, args) {});
// другой контроллер или даже директива
}
function SecondController($scope)
{
$scope.$emit('someEvent', args);
}
В обоих случаях вы также можете взаимодействовать с любой директивой.
Если вы хотите вызвать один контроллер из другого, есть четыре доступных метода:
$rootScope.$emit()
и$rootScope.$broadcast()
- Если второй контроллер является дочерним, вы можете использовать коммуникацию родитель-дитя.
- Используйте сервисы.
- Крайний метод — с помощью
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 родительского контроллера, в консоли появится значение ребенка, и наоборот.
Вопрос: Как сделать так, чтобы два контроллера в 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.
Как дополнительный ресурс, вы можете ознакомиться с этим гистом.
Для вашего вопроса о том, как можно использовать подход без сервисов в AngularJS для передачи данных между контроллерами (родительский и дочерний), приведу два примера:
Родительский и дочерний контроллер: Можно использовать
$scope
родительского контроллера для эмитации или широковещательной передачи событий. Это позволяет дочернему контроллеру реагировать на события, сгенерированные родителем. Вот пример, который иллюстрирует это решение: jsfiddle.Использование
$rootScope
: Если контроллеры не связаны друг с другом и вам нужно передавать данные между ними, вы можете использовать$rootScope
. Это глобальная область видимости, которая позволяет вам делиться данными и событиями между несвязанными контроллерами. Посмотрите этот пример для лучшего понимания: jsfiddle.
Эти примеры показывают, как передавать данные между контроллерами без создания сервисов. Однако, обратите внимание, что использование $rootScope
и эмита событий в больших приложениях может привести к недостаточной читаемости и трудностям в поддержке, поэтому стоит рассмотреть использование сервисов для более структурированного подхода.
Использование методов emit
и broadcast
действительно может быть неэффективным, поскольку события поднимаются и опускаются по иерархии скоупов, что может привести к проблемам с производительностью в сложных приложениях.
Я бы рекомендовал использовать сервис. Вот как я недавно реализовал это в одном из своих проектов - ссылка на Gist.
Основная идея заключается в регистрации системы публикации-подписки (pub-sub) или шины событий (event bus) в качестве сервиса. Затем вы можете инжектировать этот event bus везде, где вам нужно подписываться на события или публиковать их.
Получить координаты (X,Y) HTML-элемента
Прокрутка до низа div?
Работа с $scope.$emit и $scope.$on в AngularJS
Потеряно HTML-кодирование при чтении атрибута из поля ввода
jQuery/JavaScript: Замена битых изображений