Одна из особенностей $ 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()
, вместо того, чтобы смотреть в файл службы каждый раз, когда мы забываем, что она возвращает, это легко отслеживать, и это делает наш код более слабосвязанным - не зависит от текущей реализации службы. .
‹:
›› Прочтите мой следующий пост: