Pull to refresh

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

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

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

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

Сервис


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

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

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), но при этом ломается биндинг, т.к. при получении данных с сервера не происходит их прямое присвоение области видимости.

Tags:
Hubs:
+14
Comments 11
Comments Comments 11

Articles