Подтвердить что ты не робот

Является ли привязка объектов к angular $rootScope в службе плохой?

В angular у меня есть объект, который будет отображаться через мое приложение через службу.

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

Вот простой пример (который работает над jsbin здесь). Моя модель обслуживания предоставляет поля a, b и c, где c вычисляется из a + B в calcC(). Обратите внимание, что в моем реальном приложении вычисления намного сложнее, но суть здесь.

Единственный способ, с помощью которого я могу заставить это работать, - привязать мою модель обслуживания к $rootScope, а затем использовать $rootScope.$watch для просмотра любого из контроллеров, изменяющих a или b, и когда они делают, пересчитывая c. Но это кажется уродливым. Есть ли лучший способ сделать это?


Вторая проблема - производительность. В моем полном приложении a и b представлены большие списки объектов, которые объединяются до c. Это означает, что функции $rootScope.$watch будут выполнять много глубоких проверок массивов, что звучит так, что это повредит производительности.

У меня есть все, что работает с вычисленным подходом в BackBone, который максимально сокращает перерасчет, но angular, похоже, не очень хорошо работает с запланированным подходом. Любые мысли об этом тоже будут хороши.


Вот пример приложения.

var myModule = angular.module('myModule', []);

//A service providing a model available to multiple controllers
myModule.factory('aModel', function($rootScope) {
  var myModel = {
    a: 10,
    b: 10,
    c: null
  };

  //compute c from a and b
  calcC = function() {
    myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10);
  };

  $rootScope.myModel = myModel;
  $rootScope.$watch('myModel.a', calcC);
  $rootScope.$watch('myModel.b', calcC);

  return myModel;
});


myModule.controller('oneCtrl', function($scope, aModel) {
  $scope.aModel = aModel;
});

myModule.controller('twoCtrl', function($scope, aModel) {
  $scope.anotherModel = aModel;
});
4b9b3361

Ответ 1

Хотя с высокого уровня я согласен с ответом на bmleite (существует $rootScope, и использование $watch, похоже, будет работать для вашего случая использования), я хочу предложить альтернативный подход.

Используйте $rootScope.$broadcast для изменения настроек прослушивателя $rootScope.$on, который затем пересчитает значение c.

Это можно сделать вручную - т.е. когда вы будете активно изменять значения a или b или, возможно, даже в короткий промежуток времени, чтобы уменьшить частоту обновлений. Еще одним шагом будет создание "грязного" флага на вашем сервисе, так что c будет вычисляться только при необходимости.

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

Ответ 2

Я должен признать, что в первый раз, когда я прочитал ваш вопрос и увидел ваш пример, я подумал: "Это просто неправильно", однако, просмотрев его снова, я понял, что это было не так плохо, поскольку я думал, что это будет,

Посмотрим правде в глаза, $rootScope должен использоваться, если вы хотите поделиться чем-то в приложении, что идеально подходит для его создания. Конечно, вам нужно будет осторожно, это то, что разделяется между всеми областями, чтобы вы не захотели случайно изменить его. Но пусть сталкивается с этим, это не настоящая проблема, вы уже должны быть осторожны при использовании вложенных контроллеров (поскольку дочерние области наследуют свойства родительской области) и неизолированные рамки. "Проблема" уже существует, и мы не должны использовать ее в качестве оправдания, а не следовать этому подходу.

Использование $watch также кажется хорошей идеей. Это то, что инфраструктура уже предоставляет вам бесплатно и делает именно то, что вам нужно. Итак, зачем изобретать колесо? Идея в основном такая же, как и подход "изменения".

На уровне производительности ваш подход может быть фактически "тяжелым", однако он всегда будет зависеть от частоты, которую вы обновляете свойствами a и b. Например, если вы установите a или b как ng-model в поле ввода (например, на примере jsbin), c будет перерасчитываться каждый раз, когда пользователь будет что-то набирать... что явно -переработка. Если вы используете мягкий подход и обновляете a и/или b исключительно, когда это необходимо, тогда у вас не должно быть проблем с производительностью. Это будет то же самое, что и пересчет c с использованием событий "change" или подхода setter & getter. Однако, если вам действительно нужно перерасчитать c в режиме реального времени (то есть: во время ввода пользователем), проблема с производительностью всегда будет там, и это не факт, что вы используете $rootScope или $watch, что поможет улучшить его.

Возобновление, , на мой взгляд, ваш подход не плох (вообще!), просто будьте осторожны с свойствами $rootScope и избегайте обработки времени.

Ответ 3

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

Он все равно полагается на $rootScope. Однако, вместо того, чтобы передавать сообщения, он просто вызывает $rootScope.$digest.

Основной подход - предоставить единый объект комплексной модели как поле в вашей службе angular. Вы можете предоставить больше, чем считаете нужным, просто следуйте одному и тому же базовому подходу и убедитесь, что в каждом поле размещен сложный объект, ссылка на который не изменяется, т.е. Не переназначает поле новым сложным объектом. Вместо этого измените только поля этого объекта модели.

var myModule = angular.module('myModule', []);

//A service providing a model available to multiple controllers
myModule.service('aModel', function($rootScope, $timeout) {
  var myModel = {
    a: 10,
    b: 10,
    c: null
  };

  //compute c from a and b
  calcC = function() {
    myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10);
  };
  calcC();

  this.myModel = myModel;

  // simulate an asynchronous method that frequently changes the value of myModel. Note that
  // not appending false to the end of the $timeout would simply call $digest on $rootScope anyway
  // but we want to explicitly not do this for the example, since most asynchronous processes wouldn't 
  // be operating in the context of a $digest or $apply call. 
  var delay = 2000; // 2 second delay
  var func = function() {
    myModel.a = myModel.a + 10;
    myModel.b = myModel.b + 5;
    calcC();
    $rootScope.$digest();
    $timeout(func, delay, false);
  };
  $timeout(func, delay, false);
});

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

$scope.theServiceModel = aModel.myModel;

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

<div>A: {{theServiceModel.a}}</div>
<div>B: {{theServiceModel.b}}</div>
<div>C: {{theServiceModel.c}}</div>

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

Обратите внимание, что это будет работать, только если вы введете типы, наследующие объект (например, массив, пользовательские объекты) непосредственно в область видимости. Если вы вводите примитивные значения, такие как строка или число, непосредственно в область видимости (например, $scope.a = aModel.myModel.a), вы получите копию, помещенную в область видимости, и таким образом никогда не получите новое значение при обновлении. Как правило, лучше всего просто вставить весь объект модели в область видимости, как это было в моем примере.

Ответ 4

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

myModule.factory( 'aModel', function () {
  var myModel = { a: 10, b: 10 };

  return {
    get_a: function () { return myModel.a; },
    get_b: function () { return myModel.a; },
    get_c: function () { return myModel.a + myModel.b; }
  };
});

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

PS: Вы также можете обновить c, если для параметра a или b установлено значение, чтобы избежать повторного вызова в каждом вызове get_c; который лучше всего зависит от ваших деталей реализации.

Ответ 5

Из того, что я вижу в вашей структуре, имея a и b, поскольку геттеры могут быть не очень хорошей идеей, но c должна быть функцией...

Поэтому я мог бы предложить

myModule.factory( 'aModel', function () {
  return {
    a: 10,
    b: 10,
    c: function () { return this.a + this.b; }
  };
});

При таком подходе вы не можете использовать двухстороннюю привязку c к входной переменной.. Но двухсторонняя привязка c не имеет никакого смысла, потому что если вы установите значение c, как бы вы разделились значение между a и b?