AngularJS: правильный способ привязки к свойствам сервиса
Я ищу лучшую практику привязки к свойству сервиса в 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.$часы в контроллере.
$scope.$смотреть
следующий код можно просмотреть и запустить здесь: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.$ трансляция в сервисе и $корень.$on в контроллере, но в других примерах, которые я создал, которые используют $broadcast/$при первой трансляции не захватываются контроллером, но дополнительные вызовы, которые транслируются, запускаются в контроллере. Если вы знаете способ решения $rootscope.$проблема трансляции, пожалуйста, дайте ответ.
но чтобы повторить то, что я упоминал ранее, я хотел бы знать, как лучше всего привязать к сервису свойства.
обновление
этот вопрос был первоначально задан и дан ответ в апреле 2013 года. В мае 2014 года Гил Бирман предоставил новый ответ, который я изменил как правильный ответ. Поскольку ответ Гила Бирмана имеет очень мало голосов, меня беспокоит то, что люди, читающие этот вопрос, будут игнорировать его ответ в пользу других ответов с большим количеством голосов. Прежде чем вы примете решение о том, что является лучшим ответом, я настоятельно рекомендую Гил Бирман ответ.
10 ответов:
рассмотрим некоторые плюсы и минусы второго подхода:
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;
начинает звучать очень заманчиво прямо сейчас... Давайте погрузимся немного глубже в эту последнюю точку... О каких изменениях в модели мы говорили о чем? Модель на заднем конце (сервер)? Или модель, которая создается и живет только в интерфейсе? В любом случае, что по сути является Data mapping API принадлежит в уровень обслуживания переднего плана, (угловая фабрика или обслуживание). (Обратите внимание, что ваш первый пример-мои предпочтения - не имеет такого API в слой сервиса, что хорошо, потому что это достаточно просто, он не нуждается в этом.)в заключение, все не должно быть разъединено. И поскольку разметка полностью отделена от модели данных, недостатки перевешивают преимущества.
контроллеры, в общем не должны быть завалены
$scope = injectable.data.scalar
's. скорее всего, они должны быть посыпаны$scope = injectable.data
' s,promise.then(..)
и$scope.complexClickAction = function() {..}
' sв качестве альтернативного подхода для достижения развязки данных и, следовательно, инкапсуляции представления, единственное место, которое это действительно имеет смысл отделить представление от модели и с директивой. Но даже там, не
$watch
скалярные значения вcontroller
илиlink
функции. Это не сэкономит время и не сделает код более удобным для обслуживания и чтения. Это даже не сделает тестирование проще, так как робастные испытания в угловом обычно испытывают приводя DOM так или иначе. Скорее, в директиве требуют вашего API данных в форме объекта, и пользу, используя только$watch
ers, созданныеng-bind
.
пример http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio
<body ng-app="ServiceNotification"> <div style="border-style:dotted" ng-controller="TimerCtrl1"> TimerCtrl1<br/> Bad:<br/> Last Updated: {{lastUpdated}}<br/> Last Updated: {{calls}}<br/> Good:<br/> Last Updated: {{data.lastUpdated}}<br/> Last Updated: {{data.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.data = Timer.data; $scope.lastUpdated = Timer.data.lastUpdated; $scope.calls = Timer.data.calls; }; 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, 500); }; updateTimer(); return { data: data }; }); </script> </body>
обновление: я, наконец, вернулся к этому вопросу, чтобы добавить, что я не думаю, что любой подход "неправильный". Первоначально я написал, что ответ Джоша Дэвида Миллера был неправильным, но в ретроспективе его точки зрения полностью справедливы, особенно его точка зрения о разделении проблем.
разделение сторону (но косвенное отношение), есть еще одна причина для защитное копирование это я не учел. Этот вопрос в основном касается чтения данных непосредственно из службы. Но что, если разработчик в вашей команде решит, что контроллер должен преобразовать данные каким-то тривиальным способом, прежде чем представление отобразит его? (Должны ли контроллеры вообще преобразовывать данные-это еще одно обсуждение.) Если она сначала не сделает копию объекта, она может невольно вызвать регрессии в другой компонент представления, который потребляет те же данные.
что этот вопрос действительно подчеркивает, так это архитектурные недостатки типичного углового приложения (и действительно любого приложения JavaScript): тесная связь проблем и изменчивость объекта. Я недавно влюбился в архитектуру приложения с React и неизменяемые структуры данных. Это решает следующие две проблемы чудесно:
разделение: компонент потребляет все свои данные через реквизит и практически не зависит от глобальных синглетов (таких как угловые сервисы) и ничего не знает о том, что произошло выше это в иерархии представлений.
переменчивости: все реквизиты являются неизменными, что исключает риск невольной мутации данных.
Угловое 2.0 теперь находится на пути к тому, чтобы сильно заимствовать у React для достижения двух пунктов выше.
С моей точки зрения,
$watch
было бы лучшим способом практики.вы можете на самом деле упростить свой пример немного:
function TimerCtrl1($scope, Timer) { $scope.$watch( function () { return Timer.data; }, function (data) { $scope.lastUpdated = data.lastUpdated; $scope.calls = data.calls; }, true); }
Это все, что вам нужно.
так как свойства обновляются одновременно, вам нужно только один часы. Кроме того, поскольку они исходят от одного, довольно маленького объекта, я изменил его, чтобы просто смотреть
Timer.data
собственность. Последний параметр, переданный в$watch
говорит ему, чтобы проверить глубокое равенство, а не просто убедиться, что ссылка такая же.
чтобы обеспечить небольшой контекст, причина, по которой я предпочел бы этот метод для размещения значения службы непосредственно в области, заключается в обеспечении надлежащего разделения проблем. Ваш взгляд не должен знать ничего о ваших услугах для того, чтобы работать. Задача контроллера-склеить все вместе; его задача-получить данные из ваших сервисов и обработать их любым необходимым способом, а затем предоставить вашему взгляду любую специфику по необходимости. Но я не думаю, что его задача-пройти прямо по мнению. В противном случае, что контроллер вообще там делает? Разработчики AngularJS следовали тем же рассуждениям, когда они решили не включать какую-либо "логику" в шаблоны (например,
if
заявления).чтобы быть справедливым, здесь, вероятно, есть несколько точек зрения, и я с нетерпением жду других ответов.
поздно на вечеринку, но для будущих гуглеров - не используйте предоставленный ответ.
JavaScript имеет механизм передачи объектов по ссылке, в то время как он передает только мелкую копию для значений "числа, строки и т. д.".
в приведенном выше примере, вместо привязки атрибутов сервиса,почему бы нам не предоставить услугу в область?
$scope.hello = HelloService;
этот простой подход сделает угловой способный сделать двухстороннюю вязку и все волшебные вещи тебе нужно. Не взламывайте контроллер с помощью наблюдателей или ненужной разметки.
и если вы беспокоитесь о том, что ваше представление случайно перезаписывает атрибуты службы, используйте
defineProperty
чтобы сделать его читаемым, перечислимым, настраиваемым или определить геттеры и сеттеры. Вы можете получить много контроля, сделав свой сервис более твердым.последний совет: если вы тратите свое время на работу с контроллером больше, чем ваши услуги, то вы делаете это неправильно :(.
в этом конкретный демо-код, который вы предоставили, я бы рекомендовал вам сделать:
function TimerCtrl1($scope, Timer) { $scope.timer = Timer; } ///Inside view {{ timer.time_updated }} {{ timer.other_property }} etc...
Edit:
как я уже упоминал выше, вы можете контролировать поведение ваших атрибутов сервиса с помощью
defineProperty
пример:
// Lets expose a property named "propertyWithSetter" on our service // and hook a setter function that automatically saves new value to db ! Object.defineProperty(self, 'propertyWithSetter', { get: function() { return self.data.variable; }, set: function(newValue) { self.data.variable = newValue; // let's update the database too to reflect changes in data-model ! self.updateDatabaseWithNewData(data); }, enumerable: true, configurable: true });
сейчас в нашем контроллере, если мы делаем
$scope.hello = HelloService; $scope.hello.propertyWithSetter = 'NEW VALUE';
наш сервис изменит значение
propertyWithSetter
, а также задать новое значение в базу данных как-то!или мы можем принять любой подход, мы хотеть.
относятся к документация MDN на
defineProperty
.
Я думаю, что этот вопрос имеет контекстуальный компонент.
Если вы просто вытаскиваете данные из службы и излучаете эту информацию в ее представление, я думаю, что привязка непосредственно к свойству службы просто прекрасна. Я не хочу писать много boilerplate кода чтобы просто сопоставить свойства сервиса для моделирования свойств для использования в моем представлении.
кроме того, производительность в angular основана на двух вещах. Во-первых, сколько Привязок на странице. Второй насколько дороги функции геттера. Миско говорит об этом здесь
Если вам нужно выполнить конкретную логику экземпляра для данных службы (в отличие от массажа данных, применяемого в самой службе), и результат этого влияет на модель данных, подверженную представлению, тогда я бы сказал, что $watcher подходит, если функция не очень дорогая. В случае дорогостоящей функции я бы предложил кэшировать результаты в локальном (контроллере) переменная, выполняющая ваши сложные операции за пределами функции $watcher, а затем привязывающая вашу область к результату этого.
как предостережение, вы не должны висеть любой свойства непосредственно с вашего $ scope. Элемент
$scope
переменная-это не ваша модель. Он имеет ссылки на вашу модель.на мой взгляд, "лучшая практика" для простого излучения информации из службы вниз для просмотра:
function TimerCtrl1($scope, Timer) { $scope.model = {timerData: Timer.data}; };
и тогда ваш взгляд будет содержать
{{model.timerData.lastupdated}}
.
основываясь на приведенных выше примерах, я подумал, что я бы бросил способ прозрачной привязки переменной контроллера к служебной переменной.
в приведенном ниже примере изменения контроллера
$scope.count
переменная будет автоматически отражена в сервисеcount
переменной.в производстве мы фактически используем эту привязку для обновления идентификатора на службе, которая затем асинхронно извлекает данные и обновляет свои сервисные vars. Дальнейшее связывание, что означает, что контроллеры автоматически обновляться, когда служба обновляет себя.
код ниже можно увидеть работает на http://jsfiddle.net/xuUHS/163/
посмотреть:
<div ng-controller="ServiceCtrl"> <p> This is my countService variable : {{count}}</p> <input type="number" ng-model="count"> <p> This is my updated after click variable : {{countS}}</p> <button ng-click="clickC()" >Controller ++ </button> <button ng-click="chkC()" >Check Controller Count</button> </br> <button ng-click="clickS()" >Service ++ </button> <button ng-click="chkS()" >Check Service Count</button> </div>
Сервис / Контроллер:
var app = angular.module('myApp', []); app.service('testService', function(){ var count = 10; function incrementCount() { count++; return count; }; function getCount() { return count; } return { get count() { return count }, set count(val) { count = val; }, getCount: getCount, incrementCount: incrementCount } }); function ServiceCtrl($scope, testService) { Object.defineProperty($scope, 'count', { get: function() { return testService.count; }, set: function(val) { testService.count = val; }, }); $scope.clickC = function () { $scope.count++; }; $scope.chkC = function () { alert($scope.count); }; $scope.clickS = function () { ++testService.count; }; $scope.chkS = function () { alert(testService.count); }; }
Я думаю, что это лучший способ, чтобы привязка к самому сервису вместо атрибутов на нем.
вот почему:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.7/angular.min.js"></script> <body ng-app="BindToService"> <div ng-controller="BindToServiceCtrl as ctrl"> ArrService.arrOne: <span ng-repeat="v in ArrService.arrOne">{{v}}</span> <br /> ArrService.arrTwo: <span ng-repeat="v in ArrService.arrTwo">{{v}}</span> <br /> <br /> <!-- This is empty since $scope.arrOne never changes --> arrOne: <span ng-repeat="v in arrOne">{{v}}</span> <br /> <!-- This is not empty since $scope.arrTwo === ArrService.arrTwo --> <!-- Both of them point the memory space modified by the `push` function below --> arrTwo: <span ng-repeat="v in arrTwo">{{v}}</span> </div> <script type="text/javascript"> var app = angular.module("BindToService", []); app.controller("BindToServiceCtrl", function ($scope, ArrService) { $scope.ArrService = ArrService; $scope.arrOne = ArrService.arrOne; $scope.arrTwo = ArrService.arrTwo; }); app.service("ArrService", function ($interval) { var that = this, i = 0; this.arrOne = []; that.arrTwo = []; $interval(function () { // This will change arrOne (the pointer). // However, $scope.arrOne is still same as the original arrOne. that.arrOne = that.arrOne.concat([i]); // This line changes the memory block pointed by arrTwo. // And arrTwo (the pointer) itself never changes. that.arrTwo.push(i); i += 1; }, 1000); }); </script> </body>
вы можете играть в нее на этот plunker.
Я бы предпочел, чтобы мои наблюдатели были как можно меньше. Мой разум основан на моем опыте, и можно было бы утверждать это теоретически.
Проблема с использованием наблюдателей заключается в том, что вы можете использовать любое свойство в области для вызова любого из методов в любом компоненте или службе, которые вам нравятся.
В реальном мире проекта, довольно скоро вы будете в конечном итоге с не tracable (лучше сказать, трудно проследить) цепочка вызываемых методов и изменяемых значений, что специально делает посадку на борт процесс трагический.
для привязки любых данных, которые отправляет сервис не очень хорошая идея (архитектура),но если вам это нужно больше, я предлагаю вам 2 способа сделать это
1) Вы можете получить данные не внутри вас службы.Вы можете получить данные внутри вашего контроллера / директивы, и у вас не будет проблем, чтобы привязать его в любом месте
2) Вы можете использовать события angularjs.Когда вы хотите, вы можете отправить сигнал(от $rootScope) и поймать его, где вы хотите.Вы даже можете отправить данные об этом имя события.
может быть, это может помочь вам. Если вам нужно больше примеров,вот ссылка
http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html
Самые Элегантные Решения...
app.service('svc', function(){ this.attr = []; return this; }); app.controller('ctrl', function($scope, svc){ $scope.attr = svc.attr || []; $scope.$watch('attr', function(neo, old){ /* if necessary */ }); }); app.run(function($rootScope, svc){ $rootScope.svc = svc; $rootScope.$watch('svc', function(neo, old){ /* change the world */ }); });
кроме того, я пишу EDAs (Event-Driven Architectures), поэтому я склонен делать что-то вроде следующего [упрощенная версия]:
var Service = function Service($rootScope) { var $scope = $rootScope.$new(this); $scope.that = []; $scope.$watch('that', thatObserver, true); function thatObserver(what) { $scope.$broadcast('that:changed', what); } };
затем я помещаю слушателя в свой контроллер на нужный канал и просто обновляю свою локальную область таким образом.
В заключение, там не так много "лучшей практики" - скорее, его в основном предпочтение -- покуда вы держите вещи твердым и используя слабую связь. Причина, по которой я бы защищал последний код, заключается в том, что EDAs имеют самое низкое соединение возможно по своей природе. И если вы не слишком обеспокоены этим фактом, давайте не будем работать над одним и тем же проектом вместе.
надеюсь, что это помогает...