AngularJS: правильный способ привязки к свойствам услуги

Im ищет наилучшую практику того, как привязываться к свойству службы в AngularJS.

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

Ниже у меня есть два примера того, как привязываться к свойствам в службе; они оба работают. В первом примере используются основные привязки, а во втором примере используется $scope. $Watch для привязки к свойствам службы

Являются ли какие-либо из этих примеров предпочтительными при привязке к свойствам в службе или есть ли другой вариант, который Im не знал об этом, рекомендуется?

Предпосылка этих примеров заключается в том, что служба должна обновлять свои свойства "lastUpdated" и "calls" каждые 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. $в контроллере, но в других примерах, которые Ive создал, которые используют $broadcast/$в первой трансляции, не захватываются контроллером, но дополнительные вызовы, которые транслируются, запускаются в контроллере. Если вам известно о способе решения проблемы $cccpcpед. $Broadcast, предоставьте ответ.

Но чтобы повторить то, что я упомянул ранее, я хотел бы знать, как лучше всего привязываться к свойствам службы.


Обновление

Этот вопрос изначально был задан и ответил в апреле 2013 года. В мае 2014 года Гил Бирман дал новый ответ, который я изменил как правильный ответ. Поскольку ответ Гил Бирман имеет очень мало голосов, я обеспокоен тем, что люди, читающие этот вопрос, будут игнорировать его ответ в пользу других ответов с гораздо большим количеством голосов. Прежде чем принять решение о том, какой лучший ответ, я настоятельно рекомендую ответить Гил Бирман.

Ответ 1

Рассмотрим несколько плюсов и минусов второго подхода:

  • 0 {{lastUpdated}} вместо {{timerData.lastUpdated}}, который так же легко может быть {{timer.lastUpdated}}, о котором я мог бы утверждать, более читабельным (но не спорю... я ' m, давая этой точке нейтральный рейтинг, поэтому вы сами решаете)

  • +1 Может быть удобно, что контроллер действует как своего рода API для разметки, так что если каким-то образом структура модели данных изменится, вы можете (теоретически) обновить сопоставления API-контроллера, не касаясь частичного частица html.

  • -1 Однако теория не всегда практикуется, и обычно мне приходится изменять разметку и логику контроллера, когда все равно требуются изменения. Поэтому дополнительные усилия по написанию API отрицают его преимущество.

  • -1 Кроме того, этот подход не очень суровый.

  • -1 Если вы хотите привязать данные к ng-model, ваш код станет еще меньше СУХОЙ, поскольку вам нужно повторно упаковать $scope.scalar_values в контроллер, чтобы создать новый REST.

  • -0.1 Там крошечный удар производительности, создающий дополнительный наблюдатель (ы). Кроме того, если свойства данных привязаны к модели, которые не нуждаются в наблюдении в конкретном контроллере, они создадут дополнительные накладные расходы для глубоких наблюдателей.

  • -1 Что делать, если нескольким контроллерам нужны одни и те же модели данных? Это означает, что у вас есть несколько API для обновления при каждом изменении модели.

$scope.timerData = Timer.data; начинает звучать могучим соблазном прямо сейчас... Пусть погрузится немного глубже в эту последнюю точку... О каких изменениях в модели мы говорили? Модель на сервере (сервер)? Или модель, которая создана и живет только в интерфейсе? В любом случае, по сути, API сопоставления данных относится к интерфейсу уровня обслуживания (angular factory или услуге). (Обратите внимание, что ваш первый пример - мои предпочтения - не имеет такого API на уровне сервиса, что прекрасно, потому что он достаточно прост, он ему не нужен.)

В заключение все не нужно развязывать. И что касается развязки разметки полностью из модели данных, недостатки перевешивают преимущества.


Контроллеры, вообще, не должны быть завалены $scope = injectable.data.scalar. Скорее, их следует посыпать $scope = injectable.data 's, promise.then(..)' и $scope.complexClickAction = function() {..} 's

В качестве альтернативного подхода для достижения развязки данных и, таким образом, для инкапсуляции изображений, единственное место, в котором действительно имеет смысл отделить представление от модели, с директивой. Но даже там не скажите $watch скалярные значения в функциях controller или link. Это не сэкономит время или сделает код более удобной и удобочитаемой. Это даже не упростит тестирование, так как надежные тесты в angular обычно проверяют итоговую DOM в любом случае. Скорее, в директиве требуется ваш API данных в виде объекта и предпочитает использование только $watch, созданных 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>

ОБНОВЛЕНИЕ. Наконец, я вернулся к этому вопросу, чтобы добавить, что я не думаю, что любой из этих подходов является "неправильным". Первоначально я написал, что ответ Джоша Дэвида Миллера был неправильным, но в ретроспективе его точки полностью верны, особенно его точка зрения о разделении проблем.

Разделение проблем в сторону (но касательно связанных), есть еще одна причина для оборонительного копирования, которое я не рассматривал. Этот вопрос в основном касается чтения данных непосредственно из службы. Но что, если разработчик в вашей команде решит, что контроллеру необходимо каким-то тривиальным образом преобразовать данные, прежде чем представление отобразит его? (Независимо от того, должны ли контроллеры преобразовывать данные вообще, это еще одно обсуждение.) Если она не делает копию объекта сначала, она может невольно вызвать регрессии в другом представлении, который потребляет одни и те же данные.

Этот вопрос действительно подчеркивает архитектурные недостатки типичного приложения angular (и действительно любого приложения JavaScript): жесткое взаимодействие проблем и изменчивость объекта. Недавно я влюбился в приложение для архитектуры с React и неизменяемыми структурами данных. Это позволяет решить две следующие проблемы:

  • Разделение проблем: Компонент потребляет все данные через реквизиты и мало зависит от глобальных синглетов (таких как angular services) и ничего не знает о том, что произошло над ним в иерархии представлений.

  • Mutability: все реквизиты неизменяемы, что устраняет риск невольной мутации данных.

Angular 2.0 теперь находится в пути, чтобы заимствовать средства от React для достижения двух вышеперечисленных пунктов.

Ответ 2

С моей точки зрения, $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).

Чтобы быть справедливым, здесь, вероятно, есть несколько перспектив, и я с нетерпением жду других ответов.

Ответ 3

Поздно, но для будущих гуглеров Не используйте предоставленный ответ.

js имеет уникальный механизм передачи объектов по ссылке, в то время как он пропускает только мелкую копию для значений "числа, строки и т.д.".

В приведенном выше примере вместо привязки атрибутов службы. Почему мы не предоставляем службу области видимости?

$scope.hello = HelloService;

Этот простой подход сделает angular возможность привязки 2way и всех магических вещей, которые вам нужны. Не взламывайте контроллер ur с помощью наблюдателей или нераспределенной разметки.

И в случае, если вы обеспокоены тем, что ваш взгляд случайно перезаписал ваши атрибуты сервиса. Тонкое использование DefineAttribute(), чтобы сделать его, читаемым, enummratable, настраиваемым, установить геттеры и сеттеры. U это имя. вы можете получить большой контроль, сделав свой сервис более солидным.

Окончательный совет: если вы больше времени проводите на своем контроллере, то ваши услуги. то вы делаете это неправильно: (.

в этом конкретном демо-коде, который вы указали, я бы порекомендовал вам сделать

 function TimerCtrl1($scope, Timer) {
  $scope.timer = Timer;
 }
///Inside view
{{ timer.time_updated }}
{{ timer.other_property }}
etc...

Слишком много для 3-летнего поста, но если кому-то нужна дополнительная информация, дайте мне знать

Изменить:

как я уже упоминал выше, вы можете управлять поведением ваших атрибутов сервиса с помощью defineProperty

Пример:

// Lets expose a property named "propertyWithSetter" on our service
// and hook a setter function that automaticly save new value to db !
Object.defineProperty(self, 'propertyWithSetter', {
  get: function() { return self.data.variable; },
  set: function(newValue) { 
         self.data.variable = newValue; 
         //lets update the database too to reflect changes in data-model !
         self.updateDatabaseWithNewData(data);
       },
  enumerable: true,
  configurable: true
});

теперь в out contorller, если мы выполняем

$scope.hello = HelloService;
$scope.hello.propertyWithSetter = 'NEW VALUE';

наш сервис изменит значение propertyWithSetter, а также опубликует новое значение в базе данных так или иначе!

или мы можем использовать любой подход, который мы хотим. пожалуйста, обратитесь к https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Спасибо за upvotes:)

Ответ 4

Я думаю, что этот вопрос имеет контекстный компонент.

Если вы просто извлекаете данные из службы и излучаете эту информацию в нее, я думаю, что привязка непосредственно к свойству службы просто прекрасна. Я не хочу писать много кода шаблона, чтобы просто сопоставить свойства сервиса с параметрами модели, которые будут потреблять на мой взгляд.

Кроме того, производительность в angular основана на двух вещах. Во-первых, сколько привязок находится на странице. Во-вторых, насколько дорогими являются функции геттера. Мишко говорит об этом здесь

Если вам нужно выполнить специфичную для экземпляра логику данных службы (в отличие от массива данных, применяемого в самой службе), и результат этого влияет на модель данных, подверженную просмотру, тогда я бы сказал, что $watcher при условии, что функция не очень дорога. В случае дорогостоящей функции я предлагаю кэшировать результаты в локальной (для контроллера) переменной, выполняя сложные операции за пределами функции $watcher, а затем привязывая вашу область к результату.

Как предостережение, вы не должны навешивать любые свойства непосредственно из области $. Переменная $scope НЕ является вашей моделью. Он имеет ссылки на вашу модель.

На мой взгляд, "лучшая практика" для простого извлечения информации из службы вниз для просмотра:

function TimerCtrl1($scope, Timer) {
  $scope.model = {timerData: Timer.data};
};

И тогда ваше представление будет содержать {{model.timerData.lastupdated}}.

Ответ 5

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

В приведенном ниже примере изменения в переменной Controller $scope.count автоматически отражаются в переменной Service count.

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

Код ниже можно увидеть, работая на 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);
    };

}

Ответ 6

Я думаю, что это лучший способ привязки к самой службе вместо атрибутов на ней.

Вот почему:

<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.

Ответ 7

Я предпочитаю держать своих наблюдателей как можно меньше. Моя причина основана на моем опыте, и можно теоретически утверждать это.
Проблема с использованием наблюдателей заключается в том, что вы можете использовать любое свойство в области для вызова любого из методов любого компонента или услуги, которые вам нравятся.
В реальном мире проект довольно скоро вы в конечном итоге получите не трассируемую цепочку вызываемых методов (лучше сказать трудно проследить) и изменяемые значения, что особенно делает трафик на посадке трагическим.

Ответ 8

Чтобы привязать любые данные, которые отправляют службу, это не очень хорошая идея (архитектура), но если вам это нужно больше, я предлагаю вам 2 способа сделать это

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

2) вы можете использовать события angularjs. Когда вы хотите, вы можете отправить сигнал (из $rootScope) и поймать его там, где вы хотите. Вы даже можете отправить данные об этом событииName.

Может быть, это может вам помочь. Если вам нужно больше с примерами, вот ссылка

http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html

Ответ 9

Что насчет

scope = _.extend(scope, ParentScope);

Где ParentScope - это внедренная служба?

Ответ 10

Самые элегантные решения...

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 */ });
});

Кроме того, я пишу EDA (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);
    }
};

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

В заключение, не так много "лучшей практики" - скорее, в основном ее предпочтения - пока вы держите вещи SOLID и используете слабую связь. Причина, по которой я буду защищать последний код, заключается в том, что EDA имеют самую низкую взаимосвязь, применимую по своей природе. И если вы не слишком обеспокоены этим фактом, давайте избежим совместной работы над одним проектом.

Надеюсь, что это поможет...