- PVSM.RU - https://www.pvsm.ru -
В прошлой статье [1] я рассказывал про свое первое знакомство с AngularJS. С тех пор прошел уже год, сейчас у меня новые проекты и другие, часто менее тривиальные задачи. Здесь я опишу несколько нюансов, с которыми мне пришлось столкнуться в процессе работы над одной из систем. Надеюсь, читатели смогут извлечь пользу из моих практик.
Предположим, что нам поступила задача разработать клиентскую часть для нашего нового проекта. Это каталог, в котором будут храниться сотни тысяч документов. Поскольку он довольно большой, в API предусмотрена возможность загружать элементы постранично (с указанием начального индекса) а также фильтровать по отдельным полям в документе.
А для того, чтобы пользователи не терялись в системе и могли делиться между собой информацией, клиент должен сохранять свое состояние в адресной строке.
Что ж, задание понятно. Приступаем.
Чтобы хранить значения фильтров, создадим сервис $query. У него будет два метода:
Здесь следует сделать отступление. Поскольку на странице используется несколько шаблонов (например, для пагинации), в адресную строку автоматически добавляется решетка (#). Это происходит из-за того, что ng-include [2] использует сервис $location [3], при наличии которого angular начинает считать, что мы делаем одностраничное приложение.
Соответственно, объект вида
{
index: 0,
size: 20
}
превратится в
http://localhost:1337/catalog#?index=0&size=20
Но постойте. Пользователи хотят не только получать состояние страницы, но и отмечать на ней отдельный документ.
Официальная документация в таком случае советует использовать $anchorScroll [4] или scrollTo.
Т.е. теперь мы получим следующее:
http://localhost:1337/catalog#?index=0&size=20&scrollTo=5
В этот момент мое эстетическое чувство воззвало к поиску другого решения.
Первой мыслью было отказаться от ng-include, чтобы адресная строка больше не подвергалась насилию со стороны ангуляра. Но что тогда делать с шаблонами? Выход был только один: написать собственную директиву для работы с шаблонами.
С директивой проблем не возникло. Для работы с шаблонами angular использует сервис $templateCache [5]. В него можно положить кусок html-кода с помощью text/ng-template [6] или метода put(). Также, по аналогии с ng-include, предусмотрим выполнение кода из атрубита onload.
Код директивы:
app.directive('template', ['$templateCache', '$compile', function ($templateCache, $compile) {
return {
scope: false,
link: function (scope, element, attrs) {
var tpl = $compile($templateCache.get(attrs.orgnTemplate))(scope);
tpl.scope().$eval(attrs.onload || '');
element.after(tpl);
element.remove();
}
}
}]);
Теперь мы сможем использовать шаблоны следующим образом:
<div data-template="paging" data-onload="foo = 'bar'">
Решив проблему с $location, я немного переписал сервис $query, чтобы теперь он работал исключительно с history API.
К слову, не пытайтесь использовать их вместе. Это приведет к бесконечному циклу.
Так что теперь адресная строка получилась более понятной и приятной на вид:
http://localhost:1337/catalog?index=0&size=20#5
И перемещение по якорям больше не требует дополнительного кода.
Разбив страницу на отдельные шаблоны и контроллеры, я неожиданно столкнулся с другой проблемой: контроллеры должны взаимодействовать между собой. Даже если не состоят в родительских отношениях. А в отдельных случаях (опять же, пагинация), контроллеры должны синхронизировать свое состояние.
Первый вариант заключался во взаимодействии между контроллерами через события. В таком случае на каждое действие контроллеры рассылают друг-другу события. К сожалению, в моем случае кол-во и разнообразие событий на квадратный сантиметр кода стало переходить все разумные рамки. Я решил отказаться от оптимизации и сделать отдельный механизм для обмена информацией вне зависимости от текущего scope.
Так появился сервис $store. В первом варианте у него был один метод:
В контроллеры был добавлен следующий код:
$scope.$watch(function () {
return $store.value('foo');
}, function (data) {
doSomething(data);
}, true);
Теперь, когда мне требовалось синхронизировать состояние двух и более контроллеров, я лишь перезаписывал значение в ключе:
$store.value('stream', data);
Не забывайте, что все сервисы являются синглтонами, поэтому при добавлении сервиса одновременно в несколько контроллеров, мы получаем доступ к одному и тому же объекту.
Позже, когда я немного автоматизировал передачу данных между двумя шаблонами (например, список элементов теперь автоматически привязывался к пагинации с помощью своего $id), в сервис был добавлен метод alias():
Таким образом, у меня появилась возможность указывать alias в атрибуте onload директивы шаблона. Грубо говоря, если в контроллере вдруг появляется потребность запросить состояние, это можно сделать не по оригинальному ключу, который может быть недоступен, а по заранее заданному значению.
Так вышло, что, казалось бы, тривиальная задача переросла в полноценный рефакторинг. Впрочем, по его окончании, по крайней мере, на мой взгляд, код стал гораздо проще для чтения и более предсказуем в работе. Я больше не теряюсь в бесконечных событиях, питаюсь только здоровой пищей и занимаюсь спортом. Надеюсь, эта статья поможет другим обрести душевный покой и научиться чему-то новому. Удачи и приятного дня!
Автор: 5angel
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/64516
Ссылки в тексте:
[1] прошлой статье: http://habrahabr.ru/post/182348/
[2] ng-include: https://docs.angularjs.org/api/ng/directive/ngInclude
[3] $location: https://docs.angularjs.org/api/ng/service/$location
[4] $anchorScroll: https://docs.angularjs.org/api/ng/service/$anchorScroll
[5] $templateCache: https://docs.angularjs.org/api/ng/service/$templateCache
[6] text/ng-template: https://docs.angularjs.org/api/ng/directive/script
[7] Источник: http://habrahabr.ru/post/229225/
Нажмите здесь для печати.