Передача данных между сущностями AngularJS

Один из самых распространенных вопросов по AngularJS – передача данных от одной сущности другой. На самом деле вариантов не так много. Попробуем их все рассмотреть.

Для начала определимся какими типами сущностей будем оперировать: это будет контроллер, сервис и представление.

Из контроллера в представление

Посредством $scope

[javascript]
$scope.name = ‘Bob’;
[/javascript]

либо для нового синтаксиса контроллера через this в контроллере:

[javascript]
this.name = ‘Bob’;
[/javascript]

а в шаблоне представления получим через переменную:

[html]
<h1>Hi, {{name}}</h1>
[/html]

для нового синтаксиса:

[html]
<div ng-controller="userController as user">
<h1>Hi, {{user.name}}</h1>
</div>
[/html]

Код примера тут.

Из представления в контроллер

Для этого мы используем two-way data binding с помощью директив ng-bind и ng-model в представлении:

[html]
<input ng-model="name.first" />
[/html]

теперь при изменении значения поля ввода будет изменяться name.first значение в контроллере.

Пример тут.

Из представления в представление

Фактически осуществляется посредством контроллера (рассмотрено выше), но переменную-свойство в контроллере можем не создавать: она будет создана автоматически. Итого имеем связь представление – контроллер – представление. Важно, чтобы 2 элемента представления, которые собираемся синхронизировать, находились в рамках одного контроллера.

[html]
<div ng-controller="userController">;
<input ng-model"name.first">;
<textarea ng-model="name.first">;
</div>
[/html]

Пример тут.

Из контроллера в сервис

тут все просто: инжектим сервис в контроллер и задаем значение свойству, либо вызываем сеттер-метод (что ИМХО более верно):

[javascript]
app.controller(‘userController’, function (nameStorage) {
nameStorage.setName(‘Alice’);
alert(nameStorage.getName());
})
.service(‘nameStorage’, function () {
var _name = ‘Bob’;
return {
setName: function (name) {
_name = name;
},
getName: function () {
return _name;
}
}
});
[/javascript]

Пример тут.

Из сервиса в контроллер

Мы не можем изменять данные контроллера в сервисе, но мы можем “залинковать” объект-свойство сервиса на свойство контроллера, тогда в случае изменений внутри этого объекта – данные контроллера также будут обновлены:

[javascript]
app.controller(‘userController’, function ($scope, nameStorage) {
$scope.name = nameStorage.name;
alert($scope.name.first + ‘ ‘ + $scope.name.last);
setTimeout(function () {
alert($scope.name.first + ‘ ‘ + $scope.name.last);
});
})
.service(‘nameStorage’, function () {
this.name = {
first: ‘Alice’,
last: ‘Green’
};

//just to inimitate sevice change
var self = this;
setTimeout(function () {
self.name.first = ‘Bob’;
});
});
[/javascript]

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

Из сервиса в представление

Осуществляется посредствам контроллера, то есть сервис – контроллер – представление.

[javascript]
app.controller(‘userController’, function ($scope, nameStorage) {
$scope.name = nameStorage.name;
})
.service(‘nameStorage’, function ($timeout) {
this.name = {
first: ‘Alice’,
last: ‘Green’
};

//just to inimitate sevice change
var self = this;
$timeout(function () {
self.name.first = ‘Bob’;
}, 2000);
});
[/javascript]

и представление соответственно:

[html]
<div ng-controller="userController">
<h1>{{name.first + ‘ ‘ + name.last}}</h1>
</div>
[/html]

Полный пример тут.

Из представления в сервис

Аналогично предыдущему: представление – контроллер – сервис.

Пример.

Из сервиса в сервис

Один сервис инжектит другой и там вызывается сеттер-метод:

[javascript]
app
.controller(‘userController’, function ($scope, userStorage) {

})
.service(‘nameStorage’, function () {
this.name = {
first: ‘Alice’,
last: ‘Green’
};
})
.service(‘userStorage’, function (nameStorage) {
nameStorage.name = {
first: ‘Bob’,
last: ‘Brown’
};
});
[/javascript]

Пример.

Из контроллера в контроллер

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

  • из родительского контроллера в дочерний
  • и дочернего в родительский
  • из контроллера в контроллер, когда оба находятся на одном уровне (либо в разных ветках иерархии)

Рассмотрим каждый отдельно.

Из родительского контроллера в дочерний

тут также могут быть 2 случая:

  • открытый scope дочернего контроллера
  • изолированный scope дочернего контроллера

В случае с открытым – все просто: дочерний контроллер наследует свойства scope родителя по умолчанию:

[html]
<div ng-controller="userController">
<div ng-controller="nameController">
</div>
</div>
[/html]

– как видим nameController является дочерним по отношению к userController, поэтому мы легко можем получить доступ к свойствам родителя:

[javascript]
app
.controller(‘userController’, function ($scope) {
$scope.name = ‘Bob’;
setTimeout(function(){
$scope.name = ‘Alice’;
});
})
.controller(‘nameController’, function ($scope) {
alert($scope.name);
setTimeout(function(){
alert($scope.name);
});
});
[/javascript]

Пример.

С случае с изолированным scope(который мы можем получить при создании директивы) нам необходимо “залинковать” необходимые свойства родительского в дочерний. Представление будет выглядеть следующим образом:

[html]
<div ng-controller="userController">
<div user name="{{name}}"></div>
</div>
[/html]

а код директивы и контроллера:

[javascript]
var app = angular.module(‘foo’, [])

app
.controller(‘userController’, function ($scope) {
$scope.name = ‘Bob’;
setTimeout(function(){
$scope.name = ‘Alice’;
$scope.$apply()
});
})
.directive(‘user’, function () {
return {
scope: {
name: ‘@’
},
link: function(scope){
alert(scope.name);
setTimeout(function(){
alert(scope.name);
});
}
};
});
[/javascript]

В этом месте мы описываем связывание:

[javascript]
scope: {
name: ‘@’
},
[/javascript]

– это означает, что name будет взято из атрибута директивы.

Полный код.

Из дочернего контроллера в родительский

Вот этот вариант уже поинтереснее, тут не все так просто, ибо не смотря на то, что мы можем получить доступ к свойствам scope родителя, этот доступ будет только на чтение. При попытке изменить какое-либо свойство родителя, мы лишь получим создание одноименного свойства в дочернем контроллере. Это объясняется прототипной моделью наследования scope.

Возможны 2 решения:

  • изменяя не само свойство, а свойство свойства (знаю, сильно запутано, но на примере не должно быть сложно) То есть, вместо $scope.name, используем $scope.name.first.
  • через специально созданный сеттер-метод в родительском котроллере. Пример. :

[javascript]
app.controller(‘userController’, function ($scope) {
$scope.name = ‘Bob’ ;
alert($scope.name);
setTimeout(function(){
alert($scope.name);
}, 500);

$scope.setName = function(name){
$scope.name = name;
};

})
.controller(‘nameController’, function ($scope) {
setTimeout(function(){
$scope.setName(‘Alice’);
});
});
[/javascript]

Случай с изолированным скоупом полностью аналогичен предыдущему (Из родительского контроллера в дочерний), только линковка будет делаться не односторонняя:

[javascript]
scope: {
name: ‘@’
},
[/javascript]

а двунаправленная

[javascript]
scope: {
name: ‘=’
},
[/javascript]

Полный пример.

Передача данных между контроллерами находящимися на разных ветках

т.е., грубо говоря, когда мы не можем применить к ним характеристику родитель-ребенок. Решение будет заключаться в нахождении общего родительского контроллера( вплоть до $rootScope ), посредством которого и будет осуществлена передача: контроллер А – общий родительский контроллер – контроллер Б.

Передача данных дочернего и родительского контроллера уже рассмотрена выше, поэтому даю просто общий пример  связки (контроллерА – родительский контроллер – контроллерБ)

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

Общение контроллеров с помощью событий

Это на столько отдельный случай, что я решил посвятить ему целый пункт.

Сразу оговорюсь: если вы еще только начинаете работать с AngularJSне используйте этот подход. При неумелом его использовании может получиться такой “салат” из зависимостей, что потом сами не разберетесь.

Передавать события (с данными) можно посредством двух методов $scope.$emit и $scope.$broadcast. Отличие $emit от $broadcast заключается в том, что $emit передает события вверх по цепочке в скоупы всех родительских контроллеров, а $broadcast наоборот – в дочерние:

[javascript]
$scope.emitEvent = function() {
var data = { x: 5 };
$scope.$emit(‘myevent’, data);
}

$scope.broadcastEvent = function() {
var data = { y: 10 };
$scope.$broadcast(‘myevent’, data);
}
[/javascript]

Чтобы словить событие необходимо использовать метод $scope.$on.

[javascript]
$scope.$on(‘myevent’, function(data){
//…
});
[/javascript]

Полный пример работы с событиями.

Общение контроллеров с помощью специального сервиса

Существует еще один способ общения между сервисами, который напомнил Владимир Гомонов ( благодарю за дополнение! ), – это использование специального сервиса, который будет инжектиться в оба контроллера и в итоге получим связку: контроллер А – сервис – контроллер Б. Взаимодействие контроллер – сервис и сервис – контроллер уже рассмотрено выше, поэтому только выложу общий пример.

Вроде бы все возможные варианты передачи данных в рамках AngularJS перечислил, если что-то забыл – пишите – добавим.