AngularJS:控制器和工厂/服务应该如何用丰富的分层对象模型进行结构化?

AngularJS:控制器和工厂/服务应该如何用丰富的分层对象模型进行结构化?,angularjs,structure,angularjs-factory,object-model,angularjs-provider,Angularjs,Structure,Angularjs Factory,Object Model,Angularjs Provider,我读了这两篇伟大的文章: 乔纳森·克里默 及 托德座右铭 在这些文章中,作者讨论了使用控制器(使其成为视图和模型之间的桥梁)和工厂/服务(业务逻辑应该真正存在的地方)的正确方法 这是一个非常好的信息,我非常兴奋地开始重构我的一个项目中的控制器,但是我很快发现,如果你有一个丰富的对象模型,文章中显示的结构就会崩溃 以下是“重新思考Angularjs控制器”中的设置概述: 这是控制器: app.controller('InboxCtrl', function InboxCtrl (InboxFact

我读了这两篇伟大的文章:

乔纳森·克里默

托德座右铭

在这些文章中,作者讨论了使用控制器(使其成为视图和模型之间的桥梁)和工厂/服务(业务逻辑应该真正存在的地方)的正确方法

这是一个非常好的信息,我非常兴奋地开始重构我的一个项目中的控制器,但是我很快发现,如果你有一个丰富的对象模型,文章中显示的结构就会崩溃

以下是“重新思考Angularjs控制器”中的设置概述:

这是控制器:

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;
    });

});
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;
  });

});
这是工厂:

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;

});
这很好,因为
提供程序
(工厂)是单例的,所以数据可以跨视图维护,并且无需从API重新加载即可访问

如果
消息
是顶级对象,则此操作非常有效。但如果他们不这样做会发生什么呢?如果这是一个用于浏览其他用户收件箱的应用程序呢?也许您是管理员,希望能够管理和浏览任何用户的收件箱。可能您需要同时加载多个用户的收件箱。这是怎么回事?问题是收件箱消息存储在服务中,即
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
现在,
消息
是层次结构中的几个层次,它们本身没有任何意义。您不能将邮件存储在工厂中,
InboxFactory.messages
,因为您必须一次检索多个用户的邮件

现在您将有一个OrganizationFactory、一个DepartmentFactory、一个UserFactory和一个InboxFactory。检索“消息”必须在
用户的上下文中,该用户位于
部门的上下文中,该部门位于
组织的上下文中。数据应如何存储以及存储在何处?如何检索

那么这应该如何解决呢?控制器、工厂/服务和富对象模型应该如何构造

在我的思想中,我倾向于保持它的精简,而不是拥有一个丰富的对象模型。只需将对象存储在注入控制器的$scope上,如果导航到新视图,则从API重新加载。如果您需要跨视图持久化一些数据,您可以使用服务或工厂来搭建这座桥梁,但这不应该是您做大多数事情的方式


其他人是如何解决这个问题的?有没有这样的模式?

您可以使用丰富的对象模型,但是对于非顶级对象,它们的工厂应该公开一个api来创建新实例,而不是作为单例使用。这在某种程度上与你现在看到的许多应用程序的设计相反,这些应用程序比面向对象的应用程序更具功能性——我不评论这两种方法的优缺点,我也不认为角度会迫使你采用其中一种

您的示例重新设计为伪代码:

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

   var inbox = InboxFactory.createInbox();

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

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

});

如果采用基于路由的方法(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所建议的那样),在路线发生变化时(例如,用户希望查看不同的收件箱)提取必要的数据,并将这些数据直接注入相应的控制器

这种形式的另一个好处是,我们可以在实例化控制器时将数据放在手中。我们不必处理异步状态或担心在回调中放入大量代码。因此,我们可以在显示新的收件箱视图之前捕获数据加载错误,用户不会陷入半生不熟的状态


但是,进一步说到您的问题,请注意,了解富模型结构如何配合的负担已不再是控制器的问题。它只是获取一些数据,并向视图公开一系列方法。

经过大量的修补和尝试不同的方法后,我的最终决定是,您不应该跨视图持久化富对象模型

我保持对象模型超级精简,并加载每个视图所需的内容。我保留了一些高级数据(用户信息,如姓名、id、电子邮件等,以及组织数据,如他们登录的组织),但其他所有信息都会加载到当前视图中

通过这种精益方法,我的工厂将是这样的:

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 = InboxFactory.messages;

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

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

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

});
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;
  });

});
迄今为止的好处是:

  • 简化的应用程序逻辑
  • 精干、平均、轻巧(我只加载当前状态所需的内容)
  • 更少的内存使用,这意味着总体性能更好,尤其是在移动设备上

谢谢阿莱克斯玛。这就是我所倾向的。这样做,您会主张将对象图持久地存储在视图之间的某个位置吗?或者您更愿意为每个适用的视图重新创建(必要时)图形?我倾向于后者,以保持应用程序的精简和轻量级;我尽量避免