Одна из особенностей $ q service from Angular - выполнение синхронных задач асинхронным способом. Важным вариантом использования является загрузка жестко закодированных данных способом, который требует меньше изменений в коде при изменении требований - например, когда список типов жестких кодов должен быть изменен и хранится в базе данных.

Худший из возможных способов

И в то же время по-прежнему довольно популярно размещение значений непосредственно в HTML:

<select 
  ng-model="$ctrl.typeId"
>
  <option ng-value="1">Small</option>
  <option ng-value="2">Big</option>
</select>

Сегодня это даже проще, потому что начиная с версии 1.6.0 Angular поддерживает нестроковые значения с select и ng-value (что само по себе здорово). Но как можно скорее забудьте об этом квазирешении.

Хранение жестко заданных значений непосредственно в контроллере

Чаще всего это результат копирования и вставки кода:

function Controller () { 
  var $ctrl = this; 
  $ctrl.types = [
    {
      id: 1, 
      text: "Small",
    }, 
    {
      id: 2, 
      text: "Big",
    },
  ];
}
<select 
  ng-model="$ctrl.typeId" 
>
  <option 
    ng-repeat="type in $ctrl.types"
    ng-value="type.id"
  > 
    {{type.text}}
  </option>
</select>

(Лично мне нравятся конечные запятые, так что я счастлив, что они находятся в списке параметров функций в ES 2017 / JS 7!)
И это сложно поддерживать, возможные изменения необходимо вносить во многих местах, так много из нас сочли полезным:

Хранить жестко запрограммированные значения в отдельном сервисе или в виде константы

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

function TypeService () { 
  var types = [
    {
      id: 1, 
      text: "Small",
    }, 
    {
      id: 2, 
      text: "Big",
    },
  ]; 
  
  function getTypes () { 
    return angular.copy(types);
  }
 
  return { 
    getTypes: getTypes,
  };
}
// And then in controller
function Controller (TypeService) { 
  var $ctrl = this; 
  $ctrl.$onInit = function () { 
    $ctrl.types = TypeService.getTypes();
  };
}

И угловая постоянная:

app.constant("TypesConstant", [
  {
    id: 1, 
    text: "Small",
  }, 
  {
    id: 2, 
    text: "Big",
  },
  ];
);
// And then in controller
function Controller (TypesConstant) { 
  var $ctrl = this; 
  $ctrl.$onInit = function () { 
    $ctrl.types = angular.copy(TypesConstant);
  };
}

Надеюсь, вы пользуетесь компонентами и знакомы с $onInit hook: в некоторых случаях $onInit может быть действительно кстати. Выглядит красиво, доставляется из одного места, поэтому дальнейшие изменения нужно вносить только здесь. Более того, может быть, сам select должен быть составной частью? Почему бы и нет, это поможет обеспечить единообразие всего приложения (не забудьте префикс ваших директив / компонентов):

function SimpleSelectController () { 
  var $ctrl = this; 
  $ctrl.$onChanges = function (changes) { 
    if (changes.elements && Array.isArray(changes.elements)) 
      $ctrl.options = angular.copy($ctrl.elements);
  }
  $ctrl.change = function () { 
    $ctrl.$onChange({ 
      value: $ctrl.value,
    });
  }
}
app.component("raSimpleSelect", {
  controller: SimpleSelectController, 
  templateUrl: 'ra-simple-select.component.html', 
  bindings: {
    options: '<', 
    name: '@', 
    label: '@',
  },
});
//Template 
<label for="{{$ctrl.name}}">
  {{$ctrl.label}}
</label>
<select name="{{$ctrl.name}}" id="{{$ctrl.name}}" 
  ng-model="$ctrl.value" 
  ng-change="$ctrl.change()"
>
  <option 
    ng-repeat="option in options" 
    ng-value="option.value"
  >
    {{option.name}}
  </option>
</select>

И использование:

function Controller (TypesConstant) { 
  var $ctrl = this; 
  
  $ctrl.$onInit = function () { 
    var types = angular.copy(TypesConstant);
    $ctrl.types = types.map(_mapToSelectFormat);
  }
  // Because select component expects object with "name" and "value" 
  // Every array element has to be mapped
  function _mapToSelectFormat (type) { 
    return {
      name: type.text, 
      value: type.id
    };
  }
  
  $ctrl.updateModel = function (value) { 
    $ctrl.typeId = value;
  };
}
//In HTML
<ra-simple-select 
  elements="$ctrl.types" 
  on-change="$ctrl.updateModel(value)"
></ra-simple-select>

(SimpleSelectController использует $onChanges, а не $onInit, потому что $onChanges - намного лучший выбор, когда входное значение компонента загружается асинхронно)
Теперь мы уверены, что каждый select имеет label с атрибутом for, и, конечно, он может пойти дальше - мы можем инкапсулировать стили, дополнительное поведение, например, запомнить начальное значение с помощью кнопки для его восстановления… Но о чем этот абзац?

Вернемся к существу: что-то не так с сервисом / постоянным решением для жестко заданных значений?

Война никогда не меняется, но требования часто меняются

А то, что жестко запрограммировано сегодня, завтра может быть загружено с сервера / Firebase / чего угодно. Кроме того, при запуске проекта часто бывает не все (например, types это «Маленький» и «Большой», но наш заказчик хотел бы иметь дополнительные types, такие как «Очень маленький», «Средний» и «Очень большой». ). Или даже можно создать отдельный модуль для types, потому что наш клиент хочет изменить его самостоятельно. Или политика компании может заключаться в хранении любых статических значений, предоставленных клиентом, в базе данных - и я думаю, что это может быть наиболее распространенный вариант использования, но если клиент не предоставит свои значения, они будут где-то жестко закодированы.

Принцип открытости закрыт

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

function Controller (TypeService) { 
  var $ctrl = this; 
  $ctrl.$onInit = function () { 
    $ctrl.types = TypeService.getTypes();
  };
}

Станет:

function Controller (TypeService) { 
  var $ctrl = this; 
  $ctrl.$onInit = function () { 
    TypeService.getTypes().then(_bindTypes);
  }; 
  
  function _bindTypes (types) { 
    $ctrl.types = types;
  }
}

Но почему бы не рассматривать types как загруженный асинхронно с самого начала? Это избавит нас от лишних изменений в коде, который использует types, а $ q service - наш союзник.

Обработка синхронных значений как асинхронных с помощью службы $ q

Итак, как это можно сделать? На удивление просто. Контроллер загружает типы по мере их загрузки с помощью некоторого вызова AJAX:

// Other code
$ctrl.$onInit = function () { 
  TypeService.getTypes().then(_bindTypes);
}; 
  
function _bindTypes (types) { 
  $ctrl.types = types;
}

Но упомянутое ранее TypeService теперь создает обещание с жестко запрограммированным значением:

function TypeService ($q) { 
  var types = [
    {
      id: 1, 
      text: "Small",
    }, 
    {
      id: 2, 
      text: "Big",
    },
  ]; 
  
  function getTypes () { 
    return $q.when(types);
  }
 
  return { 
    getTypes: getTypes,
  };
}

И, с Controller точки зрения, нет никакой разницы между $q.when(types) и:

function getTypes () { 
  return $http.get(typeUrl).then(_getOnlyData);
} 
function _getOnlyData (response) { 
  return response.data;
}

Так что будущие изменения произойдут только в TypeService. Просто, легко понять и легко следовать.
Здесь я хочу поблагодарить Codelord за его статью $ q.defer: Вы делаете это неправильно. Я понял, что могу использовать when() вместо defer() - одну строку кода вместо четырех.

Вывод

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

:



›› Прочтите мой следующий пост: