Подтвердить что ты не робот

AngularJS: Как структурировать контроллеры и фабрики/службы с помощью богатой иерархической объектной модели?

Я прочитал эти две великие статьи:

Состояние контроллеров angularjs Джонатана Кримера

и

Переосмысление контроллеров AngularJS от Тодда Мотто

В этих статьях авторы говорят о правильном способе использования контроллеров (что делает их анемичными мостами между представлением и моделью) и фабриками/службами (где бизнес-логика должна действительно жить).

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

Здесь приведен пример настройки из "Rethinking Angularjs Controllers":

Здесь контроллер:

app.controller('InboxCtrl', function InboxCtrl (InboxFactory) {

    var vm = this;

    vm.messages = InboxFactory.messages;

    vm.openMessage = function (message) {
      InboxFactory.openMessage(message);
    };

    vm.deleteMessage = function (message) {
      InboxFactory.deleteMessage(message);
    };

    InboxFactory
      .getMessages()
      .then(function () {
      vm.messages = InboxFactory.messages;
    });

});

и здесь factory:

app.factory('InboxFactory', function InboxFactory ($location, NotificationFactory) {

  factory.messages = [];

  factory.openMessage = function (message) {
    $location.search('id', message.id).path('/message');
  };

  factory.deleteMessage = function (message) {
    $http.post('/message/delete', message)
    .success(function (data) {
      factory.messages.splice(index, 1);
      NotificationFactory.showSuccess();
    })
    .error(function () {
      NotificationFactory.showError();
    });
  };

  factory.getMessages = function () {
    return $http.get('/messages')
    .success(function (data) {
      factory.messages = data;
    })
    .error(function () {
      NotificationFactory.showError();
    });
  };

  return factory;

});

Это замечательно, и поскольку providers (factory) являются одноточечными, данные поддерживаются в разных представлениях и могут быть доступны без необходимости перезагрузки из API.

Это работает просто отлично , если messages - объект верхнего уровня. Но что происходит, если это не так? Что делать, если это приложение для просмотра входящих почтовых ящиков других пользователей? Возможно, вы являетесь администратором, и вы хотите иметь возможность управлять и просматривать почтовые ящики любого пользователя. Возможно, вам нужно одновременно загружать почтовые ящики нескольких пользователей. Как это работает? Проблема заключается в том, что сообщения inbox хранятся в службе, т.е. InboxFactory.messages.

Что делать, если иерархия выглядит так:

                           Organization
                                |
              __________________|____________________
             |                  |                    |
         Accounting       Human Resources            IT
             |                  |                    |
     ________|_______      _____|______        ______|________
    |        |       |    |     |      |      |      |        |
   John     Mike    Sue  Tom   Joe    Brad   May    Judy     Jill
    |        |       |    |     |       |     |      |        |
   Inbox    Inbox  Inbox Inbox Inbox  Inbox Inbox  Inbox    Inbox

Теперь messages находятся на нескольких уровнях в иерархии и не имеют никакого значения сами по себе. Вы не можете сохранять сообщения в factory, InboxFactory.messages, потому что вам нужно получать сообщения для нескольких пользователей за раз.

Теперь у вас будут OrganizationFactory, DepartmentFactory, UserFactory и InboxFactory. Получение "сообщений" должно быть в контексте user, который находится в контексте department, который находится в контексте organization. Как и где должны храниться данные? Как его можно получить?

Итак, как это должно быть разрешено? Как структурировать контроллеры, фабрики/службы и богатые объектные модели?

В этот момент, по моему мнению, я склоняюсь к тому, чтобы держать его в курсе и не иметь богатую объектную модель. Просто сохраните объекты в области $, введенной в контроллер, и если вы перейдете к новому представлению, перезагрузите его из API. Если вы нуждаетесь, некоторые данные сохраняются в представлениях, вы можете построить этот мост с помощью службы или factory, но это не должно быть так, как вы делаете большинство.

Как другие решили это? Существуют ли какие-либо шаблоны для этого?

4b9b3361

Ответ 1

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

Я держу объектную модель супер-тощей и загружаю только то, что мне нужно для каждого вида. Есть данные высокого уровня, которые я поддерживаю (данные пользователя, такие как имя, идентификатор, электронная почта и т.д., А также данные организации, например, в какой организации они вошли), но все остальное загружается для текущего представления.

С помощью этого тощего подхода, как бы выглядел мой factory:

app.factory('InboxFactory', function InboxFactory ($location, NotificationFactory) {

factory.messages = [];

factory.openMessage = function (message) {
  $location.search('id', message.id).path('/message');
};

factory.deleteMessage = function (message) {
  $http.post('/message/delete', message)
  .success(function (data) {
    NotificationFactory.showSuccess();
    return data;
  })
  .error(function () {
    NotificationFactory.showError();
    return null;
  });
};

factory.getMessages = function (userId) {
  return $http.get('/messages/user/id/'+userId)
  .success(function (data) {
    return data;
  })
  .error(function () {
    NotificationFactory.showError();
    return null;
  });
};

return factory;

});

И контроллер:

app.controller('InboxCtrl', function InboxCtrl (InboxFactory) {

  var vm = this;

  vm.messages = {};

  vm.openMessage = function (message) {
    InboxFactory.openMessage(message);
  };

  vm.deleteMessage = function (message) {
    InboxFactory.deleteMessage(message);
  };

  InboxFactory
    .getMessages(userId) //you can get the userId from anywhere you want.
    .then(function (data) {
      vm.messages = data;
  });

});

Преимущества:

  • упрощенная прикладная логика
  • худой и средний, легкий (я загружаю только то, что мне нужно для текущего состояния)
  • меньше использования памяти, что обеспечивает общую производительность, особенно на мобильных устройствах.

Ответ 2

Вы можете использовать богатую объектную модель, но для объектов, которые не являются топ-уровнями, их фабрики должны выставлять api для создания новых экземпляров, а не для использования в качестве одиночных. Это несколько противоречит дизайну многих приложений, которые вы видите в наши дни, которые более функциональны, чем объектно-ориентированные. Я не комментирую плюсы и минусы любого подхода, и я не думаю, что Angular заставляет вас принять тот или иной.

Ваш пример, переработанный, в псевдокоде:

app.controller('InboxCtrl', function InboxCtrl (InboxFactory) {

   var inbox = InboxFactory.createInbox();

   $scope.getMessages = function(){
      inbox.getMessages()
           .then(...)

   $scope.deleteMessages = function(){
      inbox.deleteMessages()
           .then(...)

});

Ответ 3

Ваша ситуация становится намного проще, если вы примете подход на основе маршрута (a la ngRoute или что-то подобное). Рассмотрим эту альтернативу - предупреждение непроверенного кода:

app.config(function($routeProvider) {
  $routeProvider
    .when('/inbox/:inboxId',
      templateUrl: 'views/inbox.html',
      controller: 'InboxCtrl',
      controllerAs: 'inbox',
      resolve: {
        inboxMessages: function(InboxFactory) {
          // Use use :inboxId route param if you need to work with multiple
          // inboxes. Taking some libery here, we'll assuming
          // `InboxFactory.getMessages()` returns a promise that will resolve to
          // an array of messages;
          return InboxFactory.getMessages();
        }
      }
    // ... other routes
    .otherwise: {
      // ...
    };
});

app.controller('InboxCtrl', function InboxCtrl (InboxFactory, inboxMessages) {
  var vm = this;
  vm.messages = inboxMessages;
  vm.openMessage = InboxFactory.openMessage;
  vm.deleteMessage = InboxFactory.deleteMessage;
});

Посмотрите, насколько тонкий контроллер сейчас! Конечно, я использовал несколько более компактный синтаксис в нескольких местах, но это подчеркивает, как наш контроллер действительно просто склеивает вещи.

Мы можем еще больше упростить процесс, избавившись от InboxFactory.messages, когда мы его фактически будем использовать? Нам гарантируется, что он будет заполнен после того, как InboxFactory.getMessages будет разрешен, так что пусть это обещание будет разрешено самим сообщениям.

Сохранение данных в одиночных точках таким образом может быть самым простым решением в некоторых случаях, но это затрудняет жизнь, когда эти данные должны быть получены на лету. Вам будет лучше отказаться от API и фабрик (как предлагает AlexMA), вытаскивая необходимые данные всякий раз, когда маршрут изменяется (например, пользователь хочет посмотреть на другой почтовый ящик) и вводить эти данные непосредственно в соответствующий контроллер.

Другим преимуществом этой формы является получение наших данных в момент создания экземпляра контроллера. Нам не нужно жонглировать асинхронными состояниями или беспокоиться о том, чтобы вводить много кода в обратные вызовы. В качестве следствия мы обнаруживаем ошибки загрузки данных перед отображением нового представления "Входящие", и пользователь не застревает в полуиспеченном состоянии.

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