我读了这两篇很棒的文章:
Jonathan Creamer的The state of angularjs controllers
和
Todd Motto的Rethinking AngularJS Controllers
在这些文章中,作者谈到了使用控制器的正确方法(使它们成为视图和模型之间的贫血桥梁)和工厂/服务(业务逻辑应该存在的地方)。
这是很好的信息,我很高兴能开始在我的一个项目上重构控制器,但我很快发现,如果你有一个丰富的对象模型,文章中显示的结构会崩溃。
以下是"重新思考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。正在检索"消息"必须位于user
的上下文中,该department
位于organization
的上下文中,该{{1}}位于{{1}}的上下文中。数据的存储方式和位置?该怎么回事?
那怎么解决呢?如何构建控制器,工厂/服务和丰富的对象模型?
在我的思考中,我倾向于保持精益,而不是拥有丰富的物体模型。只需将对象存储在注入控制器的$ scope中,如果导航到新视图,请从API重新加载。如果您需要某些数据在视图中保持不变,您可以使用服务或工厂构建该桥接器,但它不应该是您执行大多数事物的方式。< / p>
其他人如何解决这个问题?那有什么模式吗?
答案 0 :(得分: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(...)
});
答案 1 :(得分:2)
如果您采用基于路线的方法(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所建议的那样),在路线发生变化时(例如,用户想要查看不同的收件箱)下拉必要的数据并将数据直接注入相应的控制器。
这种形式的另一个好处是我们可以在实例化控制器时掌握我们的数据。我们不必处理异步状态或担心在回调中放入大量代码。作为必然结果,我们可以在显示新的收件箱视图之前捕获数据加载错误,并且用户不会陷入半烘焙状态。
除了你的问题之外,我们注意到了解你的丰富模型结构如何融合在一起的负担不再是控制器的问题。它只是获取一些数据并向视图公开了一堆方法。