AngularJS:控制器和工厂/服务应该如何用丰富的分层对象模型进行结构化?
我读了这两篇伟大的文章: 乔纳森·克里默 及 托德座右铭 在这些文章中,作者讨论了使用控制器(使其成为视图和模型之间的桥梁)和工厂/服务(业务逻辑应该真正存在的地方)的正确方法 这是一个非常好的信息,我非常兴奋地开始重构我的一个项目中的控制器,但是我很快发现,如果你有一个丰富的对象模型,文章中显示的结构就会崩溃 以下是“重新思考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
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(...)
});
如果采用基于路由的方法(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所建议的那样),在路线发生变化时(例如,用户希望查看不同的收件箱)提取必要的数据,并将这些数据直接注入相应的控制器
这种形式的另一个好处是,我们可以在实例化控制器时将数据放在手中。我们不必处理异步状态或担心在回调中放入大量代码。因此,我们可以在显示新的收件箱视图之前捕获数据加载错误,用户不会陷入半生不熟的状态
但是,进一步说到您的问题,请注意,了解富模型结构如何配合的负担已不再是控制器的问题。它只是获取一些数据,并向视图公开一系列方法。经过大量的修补和尝试不同的方法后,我的最终决定是,您不应该跨视图持久化富对象模型
我保持对象模型超级精简,并加载每个视图所需的内容。我保留了一些高级数据(用户信息,如姓名、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;
});
});
迄今为止的好处是:
- 简化的应用程序逻辑
- 精干、平均、轻巧(我只加载当前状态所需的内容)
- 更少的内存使用,这意味着总体性能更好,尤其是在移动设备上
谢谢阿莱克斯玛。这就是我所倾向的。这样做,您会主张将对象图持久地存储在视图之间的某个位置吗?或者您更愿意为每个适用的视图重新创建(必要时)图形?我倾向于后者,以保持应用程序的精简和轻量级;我尽量避免