
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 для реализации новых идей.