{% raw %}
Введение
Панель управления хостингом NetAngels существует уже достаточно давно и постоянно дополняется новыми возможностями или изменяет поведение старых. Все это, в совокупности, влечет за собой немалую работу, по поддержке существующего кода фронтенда и написании нового.
Сначала у нас был собственный небольшой фреймворк, основанный на JQuery-плагинах, а именно: JQuery.datatables, JQuery.vallidate и JQuery.form. С их помощью, удобно выводить информацию в виде таблиц, формировать модальные окна, работать с данными на сервере с помощью ajax. Однако каждый раз, когда нам требовалось изменить поведение того или иного виджета, приходилось переписывать десятки строк JQuery-кода.
Следующим нашим шагом был Knockout - прекрасный инструмент, расширяющий синтаксис html. По причине излишней простоты, мы вынуждены были отказаться от его использования. “Из коробки”” в Knockout не предусмотрена модульность, отсутствует роутер и инфраструктура для тестирования, а количество builtin-функций оставляет желать лучшего. Примерно в это время, мы задумались о внедрении в код панели мощного инструмента для работы с динамическими моделями данных. Выбор пал на AngularJS.
На сегодняшний день, про AngularJS написано уже немало материала, с базовым понятиями вы можете ознакомиться в руководстве разработчика и описании API.
Angularjs в панели управления
Разобрать все виджеты, написанные на angularjs, в данной статье нет возможности, в силу их количества. Для примера, рассмотрим один из важнейших элементов пользовательского интерфейса панели - таблицы. Первая важная задача таблиц - это удобное отображение данных c поиском, сортировкой, пагинацией, группировкой элементов и пр. Но самое важное - возможность легко добавлять новый функционал для любых таблиц. Посмотрим на таблицу редиректоров:
<netangels-table
ng-controller="RedirectorTableController"
titles="['Откуда (домен)', 'Куда (ссылка)']"
sizes="[120, 120]"
fields="['source', 'destination']"
actions="{'add': {'name': 'Добавить', url: 'путь для создания'},
'edit': {'name': 'Изменить', url: 'путь для изменения элемента',
'delete': {'name': 'Удалить', url: 'путь для удаления элемента'}}"
main-item-actions="['delete', 'edit']"
item-actions="['edit', 'delete']"
verbose-name="редиректор">
</netangels-table>
Данный код преобразуется в таблицу с двумя колонками, благодаря директиве “netangelsTable”, которая принимает в качестве аргументов множество данных через атрибуты тега “netangels-table”. Далее в ход идет, связанный с директивой, контроллер “NetangelsTableController”, описывающий всю логику таблицы (например сортировку элементов).
Подробнее разберем некоторые атрибуты данного тега:
- ng-controller: дополнительный контроллер, добавляющий новый функционал к таблице редиректоров
- title: заголовки колонок
- fields: поля объекта, который необходимо отобразить
- actions: действия, которые поддерживает таблица. По-умолчанию, действия вызывают форму по указанному URL, но могут совершать и любые действия на фронтенде, без обращения к серверу. Переопределить поведение стандартных действий или создать новое, можно в дополнительном контроллере (в данном случае в “RedirectorTableController”).
Перейдем к шаблону таблиц и, на примере сортировки, посмотрим как происходит отрисовка.
<th ng-repeat="title in titles">
<div ng-class="{sorting_asc: sortBy === sortFields[$index] && !sortReverse,
sorting_desc: sortBy === sortFields[$index] && sortReverse}"
ng-click="sortItems(sortFields[$index])">
<span once-title="title" once-style="{'max-width': sizes ? sizes[$index] : auto}" once-text="title"></span>
</div>
</th>
Как видно из кода, шапка таблицы рисуется с помощью директивы “ng-repeat”, а “titles” - это то самое значение атрибута “titles”, которое мы передали в html. С помощью атрибута “ng-class” мы устанавливаем необходимый класс каждому столбцу. Чтоб запустить процесс сортировки нужно воспользоваться директивой “ng-click”. Таким образом, клик по объекту вызовет функцию sortItems, передав в качестве аргумента имя поля. Реализация sortItems в контроллере выглядит следующим образом:
$scope.sortItems = function (sortKey) {
if ($scope.sortBy === sortKey) {
$scope.sortReverse = !$scope.sortReverse;
}
else {
$scope.sortReverse = false;
$scope.sortBy = sortKey;
}
};
Вот так, очень просто и декларативно. Помимо таблиц, мы использовали angular при разработке виджетов на “Рабочем столе”, VNC-консоли, меню панели, загрузчика файлов, модальных окон, форм и системы оповещения пользователей.
Возникшие проблемы
Во время разработки фронтенда новой панели на AngularJS, мы также столкнулись и с рядом проблем:
-
Как и у любого другого инструмента разработки, у angular есть порог вхождения, и в данном случае он довольно высок. Чтоб написать даже более-менее простое приложение, необходимо как минимум прочитать руководство разработчика и знать API, в отличии от того же Knockout, где все интуитивно понятно. Пожалуй, это самый большой недостаток.
-
Иногда содержимое ng-html может содержать вложенные директивы, контроллеры, которые по-умолчанию игнорируются angular’ом. Чтоб добиться от него правильного поведения, нами была написана директива compileTemplate, которая при изменении содержимого вызывала ре-компиляцию.
angular.module('netangelsApp').directive('compileTemplate', ['$compile', function ($compile) { return { link: function(scope, element, attr) { scope.$watch('ngBindHtml', function () { $compile(element, null, -9999)(scope); scope.$emit('compileTemplateComplete'); }); } }; }]);
-
Нам не удалось подружить RequireJS и AngularJS, по причине зависимости между внешним видом панели и angular-приложением. Между загрузкой с помощью RequireJS и инициализацией angular-приложения проходит достаточное время, чтобы человек заметил “дерганье” некоторых элементов. Конечно данную проблему можно решить показывая “прелоадер” при загрузке приложения, а в дальнейшем подгружать страницы ajax-ом, попутно используя роутер. Однако сейчас, к сожалению, такой возможности нет.
-
При выводе списков с большим количество элементов, можно заметить потерю производительности (по сравнению с тем же knockoutjs). Происходит это из-за так называемого “dirty-checking” лежащего в основе AngularJS. Для решения данной проблемы можно попытаться уменьшить количество watcher’ов, или же отображать список частями.
-
Размер последней стабильной версии(1.2.15) в сжатом состоянии составил 104.5 Kb, тем самым “обогнав” JQuery.
Полезные ссылки
При разработке фронтенда нашей панели управления хостингом было написано сотни строк собственного кода, но для определенных задач мы использовали несколько “angular-плагинов”, вот некоторые из них:
Angular-once
Когда angular-приложению приходится выводить огромное количество данных на странице, могут возникнуть проблемы с производительностью. Это связано с механизмом связывания данных и их отображения. А именно, каждый раз, когда встречается конструкция вида:
<div ng-bind="variableName"></div>
или
<div>{{ variableName }}</div>
angular создает watch-объект, который “наблюдает” за изменением модели и обновляет ее представление (DOM-элемент). Чем больше таких объектов создано - тем больше нагрузка, т.е. перед нами возникает задача минимизации watch-объектов. В панели управления присутствует меню с пунктами, названия и URL’ы которых,не меняются, а значит watcher’ы, в данном случае, не нужны. Вместо предыдущего примера достаточно подключить angular-once и написать.
<div once-text="variableName"></div>
Angular-file-upload
Очень удобный загрузчик, с поддержкой как современных браузеров (файлы загружаются через FormData), так и “динозавров”, вроде IE8 (загрузка происходит с помощью iframe).
Angular-seed
“Скелет” angularjs приложения
Angular-requirejs-seed
Форк angular-seed, но с поддержкой requirejs.
Итоги/заключение
Плюсы:
- Увеличение скорости разработки.
- Уменьшение количество кода.
- Замечательная документация.
- Постоянная поддержка инструмента.
Минусы:
- Высокий порог вхождения.
Angularjs - прекрасный фреймворк для случая, если ваше приложение подразумевает многофункциональный пользовательский интерфейс со сложной архитектурой и множеством связей.
Полезные ссылки
Официальная страница
Мануалы и видео-руководства
Статьи об использовании Angularjs на habrahabr