...

пятница, 16 августа 2013 г.

Набор методов для работы со списками в AngularJS

Часто приходится работать с примитивными списками, поэтому, чтобы не писать одни и те же методы, собрал их в одном сервисе. Немного расскажу о нем, как о примере вынесения функциональности из контроллеров.

Демка, песочница (с демкой играются многие, так что данные могут скакать)


Как видно из примера, у нас проблема: куча списков со схожей функциональностью (добавление, удаление, сортировка элементов — что еще может быть у списков :-).


Сервис




Описывать одни и те же методы в контроллерах явно не лучшая идея. К счастью, Ангуляр предлагает несколько способов вынесения общего кода с использованием сервисов. Подробнее о них можно почитать здесь.

Для нашей задачи используется наиболее простой тип: фабрика:



angular.module('oi.list', [])
.factory('oiList', function () {

return function (scope, Resource) {
scope.items = Resource.query()
scope.add = Resource.add()
...
}
}




Теперь, внедряя наш сервис в контроллер, получаем функцию, которая записывает в область видимости все необходимые методы.

.controller('MyCtrl', ['$scope', 'ListRes', function ($scope, ListRes) {

oiList($scope, ListRes);
}])


Кэширование




Хорошо, но можно еще улучшить. При получении данных аяксом ($resource, $http) Ангуляр по-умолчанию кеширует полученные данные. Это означает, например, что загрузив в ng-view страницу с данными, уйдя с нее и снова вернувшись не придется загружать данные заново, т.к. они берутся из кэша.

К сожалению, это работает только в элементарных случаях. Ангуляр кэширует именно запросы, а не модель. Т.е. загрузив и закэшировав массив данных с помощью Resource.query(), Ангуляр не возьмет данные из кэша если запросить их для отдельного элемента с помощью resArr[0].get(), потому что запрос будет уже другим. Так как кэш никак не связан с моделью, то его обновление при обновлении модели превращается в нетривиальную задачу.


Для решения этих проблем добавим в приложение сервис oiListCache типа value. В нем будет храниться ссылка на модель. Если при загрузке данных видим, что ссылка пустая, загружаем с сервера, иначе берем модель по ссылке.



.value('oiListCache', {cacheRes: {}})
.factory('oiList', ['oiListCache', function (oiListCache) {

return function (scope, cache, Resource) {
if (angular.equals(oiListCache.cacheRes[cache], [])) {
//Загружаем данные с сервера и записываем в кэш
scope.items = oiListCache.cacheRes[cache] = Resource.query();

} else {
//Загружаем данные из кэша
scope.items = oiListCache.cacheRes[cache];
}
}
}




Для каждой модели используем характеризующую ее строку cache, чтобы разные модели имели бы раздельный кэш.

Методы




Внутри модуля куча функций для работы со списками. У меня такой набор, у вас может несколько отличаться. Немного остановлюсь на методе, который будет у каждого: добавление нового элемента. Проще всего показывать элемент пользователю, когда он уже добавлен в базу и известен его айдишник. Минус в том, что пользователю придется ждать ответа с сервера.

Лучший способ — показать новый элемент сразу, а к базе привязать после получения ответа. И тут кроется большой подвох. Что делать, если пользователь удалил элемент у которого пока нет айдишника? Или одновременно добавил несколько элементов? В таком случае использую счетчик добавляемых/удаляемых элементов. При отправке запроса на добавление/удаление счетчик увеличивается, при получении ответа уменьшается. Код приводить не буду, его легко найти в песочнице.


Известные проблемы




Не планировал делать модуль открытым проектом, но раз уж выложил в статье, то и об известных мне проблемах упомяну. Тем более, вдруг кто-то посоветует что-нибудь дельное.

  1. В качестве параметров принимаются объект Resource и ключ для кэша cache. Если бы из ресурса можно было бы вытащить его имя, то оно бы отлично заменило ключ кэша. К сожалению, не представляю как его достать.

  2. При каждом изменении списка новое расположение элементов отправляется на сервер функцией sort(). Проблема в том, что без scope.$$phase || scope.$apply() отправка изменений происходит через раз.

  3. Сейчас модель записывается в область видимости под именем scope.items, которое нельзя поменять на другое. Выносить имя отдельной настройкой в параметры не хочется. Хочется в контроллере писать $scope.modelname = oiList($scope, 'list', ListRes), но при этом ломается биндинг, т.к. при получении данных с сервера не происходит их прямое присвоение области видимости.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

Отправить комментарий