Часто приходится работать с примитивными списками, поэтому, чтобы не писать одни и те же методы, собрал их в одном сервисе. Немного расскажу о нем, как о примере вынесения функциональности из контроллеров.
Демка, песочница (с демкой играются многие, так что данные могут скакать)
Как видно из примера, у нас проблема: куча списков со схожей функциональностью (добавление, удаление, сортировка элементов — что еще может быть у списков :-).
Описывать одни и те же методы в контроллерах явно не лучшая идея. К счастью, Ангуляр предлагает несколько способов вынесения общего кода с использованием сервисов. Подробнее о них можно почитать здесь.
Для нашей задачи используется наиболее простой тип: фабрика:
Теперь, внедряя наш сервис в контроллер, получаем функцию, которая записывает в область видимости все необходимые методы.
Хорошо, но можно еще улучшить. При получении данных аяксом ($resource, $http) Ангуляр по-умолчанию кеширует полученные данные. Это означает, например, что загрузив в ng-view страницу с данными, уйдя с нее и снова вернувшись не придется загружать данные заново, т.к. они берутся из кэша.
К сожалению, это работает только в элементарных случаях. Ангуляр кэширует именно запросы, а не модель. Т.е. загрузив и закэшировав массив данных с помощью
Для решения этих проблем добавим в приложение сервис
Для каждой модели используем характеризующую ее строку
Внутри модуля куча функций для работы со списками. У меня такой набор, у вас может несколько отличаться. Немного остановлюсь на методе, который будет у каждого: добавление нового элемента. Проще всего показывать элемент пользователю, когда он уже добавлен в базу и известен его айдишник. Минус в том, что пользователю придется ждать ответа с сервера.
Лучший способ — показать новый элемент сразу, а к базе привязать после получения ответа. И тут кроется большой подвох. Что делать, если пользователь удалил элемент у которого пока нет айдишника? Или одновременно добавил несколько элементов? В таком случае использую счетчик добавляемых/удаляемых элементов. При отправке запроса на добавление/удаление счетчик увеличивается, при получении ответа уменьшается. Код приводить не буду, его легко найти в песочнице.
Не планировал делать модуль открытым проектом, но раз уж выложил в статье, то и об известных мне проблемах упомяну. Тем более, вдруг кто-то посоветует что-нибудь дельное.
Демка, песочница (с демкой играются многие, так что данные могут скакать)
Как видно из примера, у нас проблема: куча списков со схожей функциональностью (добавление, удаление, сортировка элементов — что еще может быть у списков :-).
Сервис
Описывать одни и те же методы в контроллерах явно не лучшая идея. К счастью, Ангуляр предлагает несколько способов вынесения общего кода с использованием сервисов. Подробнее о них можно почитать здесь.
Для нашей задачи используется наиболее простой тип: фабрика:
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
, чтобы разные модели имели бы раздельный кэш.Методы
Внутри модуля куча функций для работы со списками. У меня такой набор, у вас может несколько отличаться. Немного остановлюсь на методе, который будет у каждого: добавление нового элемента. Проще всего показывать элемент пользователю, когда он уже добавлен в базу и известен его айдишник. Минус в том, что пользователю придется ждать ответа с сервера.
Лучший способ — показать новый элемент сразу, а к базе привязать после получения ответа. И тут кроется большой подвох. Что делать, если пользователь удалил элемент у которого пока нет айдишника? Или одновременно добавил несколько элементов? В таком случае использую счетчик добавляемых/удаляемых элементов. При отправке запроса на добавление/удаление счетчик увеличивается, при получении ответа уменьшается. Код приводить не буду, его легко найти в песочнице.
Известные проблемы
Не планировал делать модуль открытым проектом, но раз уж выложил в статье, то и об известных мне проблемах упомяну. Тем более, вдруг кто-то посоветует что-нибудь дельное.
- В качестве параметров принимаются объект
Resource
и ключ для кэшаcache
. Если бы из ресурса можно было бы вытащить его имя, то оно бы отлично заменило ключ кэша. К сожалению, не представляю как его достать.
- При каждом изменении списка новое расположение элементов отправляется на сервер функцией sort(). Проблема в том, что без
scope.$$phase || scope.$apply()
отправка изменений происходит через раз.
- Сейчас модель записывается в область видимости под именем scope.items, которое нельзя поменять на другое. Выносить имя отдельной настройкой в параметры не хочется. Хочется в контроллере писать
$scope.modelname = oiList($scope, 'list', ListRes)
, но при этом ломается биндинг, т.к. при получении данных с сервера не происходит их прямое присвоение области видимости.