Это третья статья из серии о моем опыте разработки с AngularJS . Я использовал AngularJS в течение нескольких месяцев, чтобы создать для клиента функцию «Моя панель инструментов», и на этом пути научился всему, что связано с Angular. Предыдущие статьи см. В части I: Основы и в части II: диалоги и данные .
Angular предлагает несколько способов взаимодействия с данными с сервера. Самый простой способ — использовать фабрику $ resource , которая позволяет вам взаимодействовать с RESTful -источниками данных на стороне сервера. Когда мы начинали проект My Dashboard, мы надеялись взаимодействовать с REST API, но вскоре обнаружили, что в нем нет всех необходимых нам данных. Вместо того, чтобы загрузить страницу и затем сделать еще один запрос для получения ее данных, мы решили встроить JSON в страницу. Для связи с сервером мы использовали наше проверенное решение Ajax: DWR .
В Angular-речь сервисы — это синглтоны, которые выполняют определенные задачи, общие для веб-приложений . Другими словами, это любой объект $ name, который можно вставить в контроллер или директиву. Однако, как Java-разработчик, я склонен думать о сервисах как об объектах, которые взаимодействуют с сервером. Документация Angular по созданию сервисов показывает вам различные варианты регистрации сервисов. Я использовал метод API angular.Module.
Когда я в последний раз работал над проектом, в Моей панели инструментов было только два сервиса: виджет и настройки.
Сервис виджетов
Служба виджетов используется для извлечения видимых виджетов для пользователя. Он имеет две функции, которые доступны для контроллеров: getUserWidgets(type)
и getHiddenWidgets(type)
. Первая функция используется в верхней части WidgetController
, а вторая — для диалога конфигурации, упомянутого в предыдущей статье .
Код для этой услуги находится в services.js . Основная часть логики заключается в ее filterData()
функции, где она проходит 4-этапный процесс:
- Получить все виджеты по типу, гарантируя, что они уникальны.
- Удалить виджеты, которые скрыты по предпочтениям пользователя.
- Создайте массив, упорядоченный по предпочтениям пользователя.
- Добавьте любые новые виджеты, которые не скрыты или не упорядочены .
Код для объекта Widget выглядит следующим образом:
angular.module('dashboard.services', []). factory('Widget',function ($filter, Preferences) { var filter = $filter('filter'); var unique = $filter('unique'); function filterData(array, query) { // get all possible widgets for a particular type var data = filter(array, query); data = unique(data); // remove widgets that are hidden by users preference var hidden = Preferences.getHiddenWidgets(query.type); for (var i = 0; i < hidden.length; i++) { var w = filter(data, {id: hidden[i]}); $.each(w, function (index, item) { var itemId = item.id; if (hidden.indexOf(itemId) > -1) { data.splice(data.indexOf(item), 1); } }); } // build an array that's ordered by users preference var ordered = []; var visible = Preferences.getUserWidgets(query.type); for (var j = 0; j < visible.length; j++) { var v = filter(data, {id: visible[j]}); $.each(v, function (index, item) { var itemId = item.id; if (visible.indexOf(itemId) > -1) { ordered.push(item) } }); } // loop through data again and add any new widgets not in ordered $.each(data, function (index, item) { if (ordered.indexOf(item) === -1) { ordered.push(item); } }); return ordered; } return { getUserWidgets: function (type) { return filterData(widgetData, {type: type}) }, getHiddenWidgets: function (type) { var hidden = Preferences.getHiddenWidgets(type); var widgetsForType = filter(widgetData, {type: type}); widgetsForType = unique(widgetsForType); var widgets = []; for (var j = 0; j < hidden.length; j++) { var v = filter(widgetsForType, {id: hidden[j]}); $.each(v, function (index, item) { if (widgetsForType.indexOf(item) > -1) { widgets.push(item) } }); } return widgets; } } })
Как только вы настроите сервис, подобный этому, вы можете добавить его по имени. Так , например, WidgetController
уже Widget
введен в его конструктор:
function WidgetController($dialog, $scope, Widget, Preferences) {
Сервис настроек
Сервис настроек используется для получения и сохранения пользовательских настроек. Это довольно просто, и большая часть его кода взаимодействует с DWR. Эта услуга имеет 5 методов:
- getHiddenWidgets (type) — используется сервисом Widget
- getUserWidgets (type) — используется сервисом Widget
- saveBarOrder (bars) — вызывается из WidgetController
- saveWidgetOrder (тип, виджеты) — вызывается из WidgetController
- saveWidgetPreferences (тип, виджеты) — вызывается из WidgetController
Во-первых, давайте посмотрим на save*Order()
функции. Есть две части страницы, которые используют директиву ui-sortable для инициализации функции перетаскивания. Первый находится на главном <ul>, который содержит 3 бара слева.
<ul class="widgets" ui-sortable="{handle:'.heading', update: updateBars}">
Свойство update в конфигурации JSON указывает, какой метод вызывать в контроллере. Аналогично, задачи и сводные элементы вызывают updateOrder
функцию.
<ul class="summary-items" ng-model="summaryWidgets" ui-sortable="{update: updateOrder}"> ... <ul class="task-items" ng-model="taskWidgets" ui-sortable="{update: updateOrder}">
Эти функции находятся в WidgetController
и создают массив идентификаторов виджетов для передачи в службу предпочтений.
$scope.updateBars = function(event, ui) { var bars = []; $.each($(ui.item).parent().children(), function (index, item) { bars.push(item.id.substring(0, item.id.indexOf('-'))) }); Preferences.saveBarOrder(bars); }; $scope.updateOrder = function(event, ui) { var parentId = $(ui.item).parent().parent().attr('id'); var type = parentId.substring(0, parentId.indexOf('-')); var items = []; $.each($(ui.item).parent().children(), function (index, item) { items.push(item.id.substring(item.id.indexOf('-') + 1)) }); Preferences.saveWidgetOrder(type, {items: items}); };
Порядок штрихов используется при загрузке страницы. Следующий код скриптлета находится внизу страницы приложения в его $ (document) .ready:
<% String barOrder = user.getDashboardBarSortOrder(); if (barOrder != null) { %> sortBars(['<%= barOrder %>']); <% } %>
sortBars()
Функция находится в dashboard.js файл (где мы помещаем все не-угловые функции):
function sortBars(barOrder) { // Sort bars according to user preferences $.each(barOrder, function(index, item) { var bar = $('#' + item + '-bar'); if (bar.index() !== index) { if (index === 0) { bar.insertBefore($('.widgets>li:first-child')); } else if (index === (barOrder.length - 1)) { bar.insertAfter($('.widgets>li:last-child')); } else { bar.insertBefore($('.widgets>li:eq(' + index + ')')); } } }); }
Теперь, когда вы увидели, откуда вызывается Preferences, давайте посмотрим на код службы.
Проверка на неопределенность и уникальность в приведенном ниже коде не обязательна, но я предпочитаю защитное кодирование.
factory('Preferences', function ($filter) { var unique = $filter('unique'); return { // Get in-page variable: hiddenWidgets getHiddenWidgets: function (type) { var items = hiddenWidgets[type]; return (angular.isUndefined(items) ? [] : unique(items)); }, // Get in-page variable: userWidgets getUserWidgets: function (type) { var items = userWidgets[type]; return (angular.isUndefined(items) ? [] : unique(items)); }, // Save main bar (task, summary, chart) order saveBarOrder: function (bars) { DWRFacade.saveDashboardBarSortOrder(bars, { errorHandler: function (errorString) { alert(errorString); } }) }, // Save order of widgets from sortable saveWidgetOrder: function (type, widgets) { userWidgets[type] = widgets.items; DWRFacade.saveDashboardWidgetPreference(type, widgets, { errorHandler: function (errorString) { alert(errorString); } }); }, // Save hidden and visible (and order) widgets from config dialog saveWidgetPreferences: function (type, widgets) { // widgets is a map of hidden and visible var hiddenIds = []; $.each(widgets.hidden, function (index, item) { hiddenIds.push(item.id); }); var visibleIds = []; $.each(widgets.items, function (index, item) { visibleIds.push(item.id); }); var preferences = { hidden: hiddenIds, items: visibleIds }; // reset local variables in page hiddenWidgets[type] = hiddenIds; userWidgets[type] = visibleIds; DWRFacade.saveDashboardWidgetPreference(type, preferences, { errorHandler: function (errorString) { alert(errorString); } }); } } })
Использование $ http и получение данных
В этом конкретном приложении мы не делали чтения с сервера Angular. Мы просто записывали настройки на сервер и обновляли встроенные переменные при изменении данных. Функциональность приложения в реальном времени не будет заметна, если запись не удалась.
В моем текущем проекте Angular это скорее полноценное приложение, которое читает столько же, сколько и пишет. Для этого я считаю полезным либо 1) передавать обратные вызовы сервисам, либо 2) использовать систему событий Angular для публикации / подписки на события.
Первый метод является самым простым и, вероятно, наиболее знакомым для разработчиков JavaScript. Например, вот код контроллера для удаления картинки профиля:
Profile.removePhoto($scope.user, function (data) { // close the dialog $scope.close('avatar'); // success message using toastr: http://bit.ly/14Uisgm Flash.pop({type: 'success', body: 'Your profile picture was removed.'}); })
И Profile.removePhoto()
метод:
removePhoto: function (user, callback) { $http.post('/profile/removePhoto', user).success(function (response) { return callback(response); }); }
Второй, управляемый событиями метод работает одинаково хорошо, но может легко пострадать от опечаток в именах событий.
// controller calling code Profile.getUser(); // service code getUser: function () { $http.get('/profile').success(function (data) { if (data.username) { $log.info('Profile for ' + data.username + ' retrieved!'); $rootScope.$broadcast('event:profile', data); } }); } // controller receiving code $rootScope.$on('event:profile', function (event, data) { $scope.user = data; });
Мне нравятся оба метода, но управляемый событиями кажется, что он может предложить большую расширяемость в будущем.
Резюме
Использование внутристраничных переменных и DWR не рекомендуется Angular Team. Тем не менее, это хорошо сработало для нас и кажется хорошим способом построения сервисов Angular. Даже если для получения всех данных станет доступен REST API, я думаю, что использование внутристраничных переменных для минимизации запросов — хорошая идея.
При получении данных вы можете использовать обратные вызовы или систему пабов / подпрограмм Angular ($ broadcast и $ on) для получения данных в ваших контроллерах. Если вы хотите узнать больше об этой технике, смотрите статью Эрик Терпстра « Общение с $ broadcast» . В своей статье Эрик упоминает паб / субмодуль Томаса Берлесона, который выступает в качестве очереди сообщений. Если вы использовали MessageQueue Томаса (или что-то подобное) с Angular, я хотел бы услышать о вашем опыте.
В следующей статье я расскажу о том, как мы изменили дизайн My Dashboard и использовали CSS3 и JavaScript для реализации новых идей.