30

我读了这两篇很棒的文章:

Jonathan Creamer的 angularjs 控制器状态

Todd Motto重新思考 AngularJS 控制器

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

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

以下是“重新思考 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.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(工厂)是单例的,数据在视图之间维护,并且可以访问而无需从 API 重新加载。

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

现在您将拥有一个 OrganizationFactory、一个 DepartmentFactory、一个 UserFactory 和一个 InboxFactory。检索“消息”必须在 a 的上下文中user,who 在 a 的上下文中,在 adepartment的上下文中organization。数据应该如何以及在哪里存储?应该如何取回?

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

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

其他人是如何解决这个问题的?这有什么模式吗?

4

3 回答 3

3

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

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

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

   var inbox = InboxFactory.createInbox();

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

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

});
于 2015-04-16T19:48:28.683 回答
2

如果您采用基于路线的方法(langRoute或类似的方法),您的情况会变得简单得多。考虑这个替代方案 - 警告未经测试的代码:

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 所建议的那样),在路由发生变化时(例如,用户想要查看不同的收件箱)提取必要的数据并将该数据直接注入适当的控制器。

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

关于您的问题,请注意了解您的丰富模型结构如何组合在一起的负担不再是控制器的问题。它只是获取一些数据并向视图公开一堆方法。

于 2015-04-26T18:49:14.680 回答
0

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

我保持对象模型超级精简并加载每个视图所需的内容。我保留了一些高级数据(用户信息,如姓名、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 = {};

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

});

到目前为止的好处是:

  • 简化的应用程序逻辑
  • 精益和平均,轻量级(我只加载当前状态所需的内容)
  • 更少的内存使用,这意味着整体性能更好,尤其是在移动设备上
于 2015-11-24T00:44:54.063 回答