Я ищу лучший способ привязки к свойству службы в AngularJS.
Я работал с несколькими примерами, чтобы понять, как привязать свойства к сервису, созданному с помощью AngularJS.
Ниже у меня есть два примера того, как привязать свойства в сервисе; они оба работают. В первом примере используются базовые привязки, а во втором - $ scope. $ Watch для привязки к свойствам службы.
Любой из этих примеров предпочтителен при привязке к свойствам в службе или есть другой вариант, который я не знаю, который будет рекомендован?
Предпосылкой этих примеров является то, что сервис должен обновлять свои свойства «lastUpdated» и «звонки» каждые 5 секунд. После обновления свойств службы представление должно отражать эти изменения. Оба эти примера работают успешно; Интересно, есть ли лучший способ сделать это?
Базовая привязка
Следующий код можно просмотреть и запустить здесь: http://plnkr.co/edit/d3c16z
<html>
<body ng-app="ServiceNotification" >
<div ng-controller="TimerCtrl1" style="border-style:dotted">
TimerCtrl1 <br/>
Last Updated: {{timerData.lastUpdated}}<br/>
Last Updated: {{timerData.calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.timerData = Timer.data;
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
Другой способ, которым я решил связать со свойствами сервиса, это использовать $ scope. $ Watch в контроллере.
$ Сфера. $ Часы
Следующий код можно просмотреть и запустить здесь: http://plnkr.co/edit/dSBlC9
<html>
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.$watch(function () { return Timer.data.lastUpdated; },
function (value) {
console.log("In $watch - lastUpdated:" + value);
$scope.lastUpdated = value;
}
);
$scope.$watch(function () { return Timer.data.calls; },
function (value) {
console.log("In $watch - calls:" + value);
$scope.calls = value;
}
);
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
Я знаю, что могу использовать $ rootscope. $ Broadcast в сервисе и $ root. $ On в контроллере, но в других созданных мной примерах, которые используют $ broadcast / $ в первой трансляции, не фиксируется контроллер, но дополнительные вызовы, которые передаются, запускаются в контроллере. Если вам известен способ решения проблемы с $ rootcope. $ Broadcast, пожалуйста, предоставьте ответ.
Но чтобы перефразировать то, что я упомянул ранее, я хотел бы узнать, как лучше всего связать свойства сервиса.
Обновить
Этот вопрос был первоначально задан и получен ответ в апреле 2013 года. В мае 2014 года Джил Бирман предоставил новый ответ, который я изменил как правильный ответ. Поскольку у ответа Гила Бирмана очень мало голосов «за», я обеспокоен тем, что люди, читающие этот вопрос, проигнорируют его ответ в пользу других ответов с большим количеством голосов. Прежде чем принять решение о том, что является лучшим ответом, я настоятельно рекомендую ответ Гила Бирмана.
источник
Ответы:
Рассмотрим некоторые плюсы и минусы второго подхода :
0
{{lastUpdated}}
вместо того{{timerData.lastUpdated}}
, что может быть так же легко{{timer.lastUpdated}}
, как я могу поспорить, более читабельно (но давайте не будем спорить ... Я даю этой точке нейтральную оценку, чтобы вы сами решили)+1 Может быть удобно, что контроллер действует как своего рода API для разметки, так что если каким-то образом структура модели данных изменится, вы можете (теоретически) обновить отображения API контроллера, не касаясь части html.
-1 Однако теория не всегда практика , и я обычно нахожу того , чтобы изменить разметку и логику контроллера , когда изменения называются для, так или иначе . Таким образом, дополнительные усилия по написанию API сводят на нет его преимущество.
-1 Кроме того, такой подход не очень СУХОЙ.
-1 Если вы хотите привязать данные к
ng-model
своему коду, они становятся еще менее СУХИМЫМИ, так как вам нужно переупаковать их$scope.scalar_values
в контроллере, чтобы сделать новый вызов REST.-0.1 Есть крошечный хит производительности, создающий дополнительных наблюдателей. Кроме того, если к модели привязаны свойства данных, которые не нужно отслеживать в конкретном контроллере, это создаст дополнительные издержки для глубоких наблюдателей.
-1 Что если нескольким контроллерам нужны одинаковые модели данных? Это означает, что у вас есть несколько API для обновления при каждом изменении модели.
$scope.timerData = Timer.data;
сейчас это звучит очень заманчиво ... Давайте немного углубимся в этот последний момент ... О каких изменениях в модели мы говорили? Модель на серверной части (сервер)? Или модель, которая создана и живет только в передней части? В любом случае то, что по существу является API отображения данных, относится к внешнему уровню обслуживания (угловая фабрика или служба). (Обратите внимание, что ваш первый пример - мои предпочтения - не имеет такого API на уровне сервисов , что хорошо, потому что он достаточно прост и не нуждается в нем.)В заключение , все не должно быть отделено. А что касается полного отделения разметки от модели данных, недостатки перевешивают преимущества.
Контроллеры, как правило, не должны быть засорены с
$scope = injectable.data.scalar
. Скорее, они должны быть посыпаны$scope = injectable.data
's,promise.then(..)
' s, и$scope.complexClickAction = function() {..}
'sВ качестве альтернативного подхода к разделению данных и, следовательно, к инкапсуляции представлений, единственное место, в котором действительно имеет смысл отделить представление от модели, - это директива . Но даже там не
$watch
скалярные значения в функцияхcontroller
илиlink
. Это не сэкономит время и не сделает код более понятным и удобочитаемым. Это даже не облегчит тестирование, поскольку надежные тесты в угловом режиме обычно проверяют полученный DOM в любом случае . Скорее, в директиве требуется ваш API данных в виде объекта, и вы предпочитаете использовать только те, которые$watch
созданыng-bind
.Пример http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio
ОБНОВЛЕНИЕ : я наконец вернулся к этому вопросу, чтобы добавить, что я не думаю, что любой подход "неправильн". Первоначально я писал, что ответ Джоша Дэвида Миллера был неверным, но в ретроспективе его положения полностью верны, особенно его точка зрения о разделении интересов.
Помимо разделения проблем (но косвенно связанных), есть еще одна причина для защитного копирования, которую я не смог рассмотреть. Этот вопрос в основном касается чтения данных непосредственно из службы. Но что, если разработчик в вашей команде решит, что контроллеру необходимо каким-то тривиальным образом преобразовать данные, прежде чем представление отобразит их? (Должны ли контроллеры преобразовывать данные вообще - другое обсуждение.) Если она сначала не сделает копию объекта, она может невольно вызвать регрессию в другом компоненте представления, который потребляет те же данные.
Что на самом деле подчеркивает этот вопрос, так это архитектурные недостатки типичного углового приложения (и на самом деле любого приложения JavaScript): тесная взаимосвязь проблем и изменчивость объектов. Недавно я влюбился в архитектурное приложение с React и неизменяемыми структурами данных. Это прекрасно решает две следующие проблемы:
Разделение проблем : компонент потребляет все свои данные через реквизиты и практически не зависит от глобальных синглетонов (таких как сервисы Angular) и ничего не знает о том, что произошло над ним в иерархии представлений.
Изменчивость : все реквизиты являются неизменяемыми, что исключает риск невольной мутации данных.
Angular 2.0 в настоящее время находится на пути к тому, чтобы позаимствовать у React значительные средства для достижения двух указанных выше пунктов.
источник
С моей точки зрения, это
$watch
был бы лучший способ практики.Вы можете немного упростить свой пример:
Это все, что вам нужно.
Поскольку свойства обновляются одновременно, вам нужно всего лишь один час. Кроме того, так как они происходят из одного, довольно маленького объекта, я изменил его, чтобы просто наблюдать за
Timer.data
свойством. Последний переданный параметр$watch
указывает ему проверять наличие глубокого равенства, а не просто проверять, является ли ссылка одинаковой.Чтобы обеспечить немного контекста, я бы предпочел, чтобы этот метод помещал стоимость услуги непосредственно в область действия, чтобы обеспечить надлежащее разделение интересов. Ваше мнение не должно знать ничего о ваших услугах, чтобы работать. Задача контроллера - склеить все вместе; его задача состоит в том, чтобы получать данные из ваших служб и обрабатывать их любым необходимым способом, а затем предоставлять вашему представлению всю необходимую информацию. Но я не думаю, что его работа состоит в том, чтобы просто передать сервис прямо на вид. Иначе, что вообще делает контроллер? Разработчики AngularJS следовали той же причине, когда они решили не включать какую-либо «логику» в шаблоны (например,
if
операторы).Чтобы быть справедливым, здесь, вероятно, несколько точек зрения, и я с нетерпением жду других ответов.
источник
{{lastUpdated}}
{{timerData.lastUpdated}}
Timer.data
в $ watch,Timer
должен быть определен в $ scope, потому что строковое выражение, которое вы передаете в $ watch, сравнивается с областью видимости. Вот плункер, который показывает, как заставить это работать. Параметр objectEquality здесь задокументирован - 3-й параметр - но не очень хорошо объяснен.$watch
довольно неэффективная. См. Ответы на stackoverflow.com/a/17558885/932632 и stackoverflow.com/questions/12576798/…Поздно до вечеринки, но для будущих Googlers - не используйте предоставленный ответ.
В JavaScript есть механизм передачи объектов по ссылке, в то время как он передает только поверхностную копию значений «числа, строки и т. Д.».
В приведенном выше примере, вместо того, чтобы связывать атрибуты сервиса, почему бы нам не выставить сервис в область?
Этот простой подход сделает angular способным выполнять двустороннюю привязку и все магические вещи, которые вам нужны. Не взламывайте свой контроллер наблюдателями или ненужной разметкой.
И если вы беспокоитесь о том, что ваше представление случайно перезаписывает атрибуты службы, используйте его,
defineProperty
чтобы сделать его читаемым, перечисляемым, настраиваемым или определять методы получения и установки. Вы можете получить много контроля, сделав свой сервис более надежным.Последний совет: если вы тратите свое время на работу с контроллером больше, чем на сервисы, то вы делаете это неправильно :(.
В указанном вами демонстрационном коде я бы порекомендовал вам сделать следующее:
Редактировать:
Как я уже упоминал выше, вы можете контролировать поведение атрибутов службы, используя
defineProperty
Пример:
Теперь в нашем контроллере, если мы делаем
наш сервис изменит значение,
propertyWithSetter
а также опубликует новое значение в базе данных!Или мы можем выбрать любой подход, который захотим.
Обратитесь к документации MDN для
defineProperty
.источник
$scope.model = {timerData: Timer.data};
просто прикрепив его к модели, а не прямо в Scope.Я думаю, что этот вопрос имеет контекстную составляющую.
Если вы просто извлекаете данные из службы и передаете эту информацию для просмотра, я думаю, что привязка напрямую к свойству службы - это просто замечательно. Я не хочу писать много стандартного кода, чтобы просто сопоставить свойства сервиса со свойствами модели, которые потребляются в моем представлении.
Кроме того, производительность в угловых зависит от двух вещей. Во-первых, сколько привязок на странице. Во-вторых, насколько дорогими являются функции получения. Миско говорит об этом здесь
Если вам необходимо выполнить специфичную для экземпляра логику для данных службы (в отличие от массирования данных, применяемого внутри самой службы), и результат этого влияет на модель данных, предоставляемую представлению, тогда я бы сказал, что $ watcher подходит, так как Пока функция не очень дорогая. В случае с дорогой функцией я бы предложил кэшировать результаты в локальной (для контроллера) переменной, выполняя сложные операции вне функции $ watcher, а затем связывая вашу область видимости с результатом этого.
В качестве предостережения вы не должны вешать какие-либо свойства прямо за пределы вашей области видимости.
$scope
Переменная не ваша модель. Он имеет ссылки на вашу модель.На мой взгляд, «лучшая практика» для простого распространения информации от сервиса до просмотра:
И тогда ваш взгляд будет содержать
{{model.timerData.lastupdated}}
.источник
Основываясь на приведенных выше примерах, я решил использовать прозрачную привязку переменной контроллера к служебной переменной.
В приведенном ниже примере изменения
$scope.count
переменной Controller будут автоматически отражены вcount
переменной Service .На самом деле мы используем привязку this для обновления идентификатора службы, который затем асинхронно извлекает данные и обновляет свои служебные переменные. Дальнейшее связывание означает, что контроллеры автоматически обновляются при обновлении службы.
Код ниже можно увидеть на http://jsfiddle.net/xuUHS/163/
Посмотреть:
Услуги / Контроллер:
источник
Я думаю, что это лучший способ привязать сам сервис, а не атрибуты к нему.
Вот почему:
Вы можете играть в это на этом поршне .
источник
Я бы предпочел держать своих наблюдателей как можно меньше. Моя причина основана на моем опыте, и можно утверждать это теоретически.
Проблема с использованием наблюдателей заключается в том, что вы можете использовать любое свойство в области для вызова любого из методов в любом компоненте или услуге, которая вам нравится.
В реальном проекте очень скоро вы получите неразрешимую (точнее сказать, трудно прослеживаемую) цепочку вызываемых методов и изменяемых значений, что особенно делает трагический процесс адаптации.
источник
Связывать любые данные, которые отправляет сервис, не очень хорошая идея (архитектура), но если вам это нужно больше, я предлагаю вам 2 способа сделать это
1) вы можете получить данные не в своей службе. Вы можете получить данные в вашем контроллере / директиве, и у вас не возникнет проблем с их привязкой куда-либо
2) вы можете использовать события angularjs. Когда бы вы ни захотели, вы можете отправить сигнал (из $ rootScope) и перехватить его где угодно. Вы даже можете отправить данные об этом eventName.
Может быть, это может помочь вам. Если вам нужно больше с примерами, вот ссылка
http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html
источник
Что о
Где ParentScope является внедренным сервисом?
источник
Самые элегантные решения ...
Кроме того, я пишу EDA (Event-Driven Architectures), поэтому я склонен делать что-то вроде следующего [упрощенная версия]:
Затем я помещаю слушателя в свой контроллер на желаемом канале и просто обновляю свой локальный объем таким образом.
В заключение, есть не так много «Лучшей практики» - скорее, в основном это предпочтение - до тех пор, пока вы сохраняете вещи твердыми и используете слабую связь. Причина, по которой я бы выступил за последний код, заключается в том, что EDA имеют наименьшую возможную связь по своей природе. И если вы не слишком обеспокоены этим фактом, давайте избегать совместной работы над одним проектом.
Надеюсь это поможет...
источник