我正在为我的最新项目使用AngularJS。在文档和教程中,所有模型数据都放在控制器范围内。我理解它必须在那里为控制器可用,因此在相应的视图中。
然而,我不认为这个模型应该在那里实现。例如,它可能很复杂并具有私有属性。此外,人们可能想在另一个上下文/应用程序中重用它。把所有东西都放到控制器中完全打破了MVC模式。
这同样适用于任何模型的行为。如果我要使用DCI体系结构并将行为与数据模型分离,我将不得不引入额外的对象来保存行为。这可以通过引入角色和上下文来实现。
DCI ==数据协作交互
当然,模型数据和行为可以用简单的javascript对象或任何“类”模式来实现。但是AngularJS会怎么做呢?使用服务?
所以这就归结为一个问题:
如何实现与控制器解耦的模型,遵循AngularJS的最佳实践?
DCI is a paradigm and as such there's no angularJS way of doing it, either the language support DCI or it doesn't. JS support DCI rather well if you are willing to use source transformation and with some drawbacks if you are not. Again DCI has no more to do with dependency injection than say a C# class has and is definitely not a service either. So the best way to do DCI with angulusJS is to do DCI the JS way, which is pretty close to how DCI is formulated in the first place. Unless you do source transformation, you will not be able to do it fully since the role methods will be part of the object even outside the context but that's generally the problem with method injection based DCI. If you look at fullOO.info the authoritative site for DCI you could have a look at the ruby implementations they also use method injection or you could have a look at here for more information on DCI. It's mostly with RUby examples but the DCI stuff is agnostic to that.
One of the keys to DCI is that what the system does is separated from what the system is. So the data object are pretty dumb but once bound to a role in a context role methods make certain behaviour available. A role is simply an identifier, nothing more, an when accessing an object through that identifier then role methods are available. There's no role object/class. With method injection the scoping of role methods is not exactly as described but close. An example of a context in JS could be
function transfer(source,destination){
source.transfer = function(amount){
source.withdraw(amount);
source.log("withdrew " + amount);
destination.receive(amount);
};
destination.receive = function(amount){
destination.deposit(amount);
destination.log("deposited " + amount);
};
this.transfer = function(amount){
source.transfer(amount);
};
}
我目前正在尝试这种模式,虽然不是DCI,但它提供了一个经典的服务/模型解耦(与web服务对话的服务(又名模型CRUD),以及定义对象属性和方法的模型)。
注意,我只在模型对象需要方法处理自己的属性时使用这种模式,我可能会在任何地方使用它(比如改进的getter/setter)。我并不提倡对每项服务都系统地这样做。
编辑:
我曾经认为这种模式违背了“Angular模型是普通的javascript对象”的信条,但现在对我来说,这种模式完全没问题。
编辑(2):
更清楚地说,我使用Model类只分解简单的getter / setter(例如:在视图模板中使用)。对于大的业务逻辑,我建议使用单独的服务,这些服务“知道”模型,但与模型保持分离,并且只包括业务逻辑。如果愿意,可以将其称为“业务专家”服务层
service/ElementServices.js(注意Element是如何在声明中被注入的)
MyApp.service('ElementServices', function($http, $q, Element)
{
this.getById = function(id)
{
return $http.get('/element/' + id).then(
function(response)
{
//this is where the Element model is used
return new Element(response.data);
},
function(response)
{
return $q.reject(response.data.error);
}
);
};
... other CRUD methods
}
model/Element.js(使用angularjs Factory,用于对象创建)
MyApp.factory('Element', function()
{
var Element = function(data) {
//set defaults properties and functions
angular.extend(this, {
id:null,
collection1:[],
collection2:[],
status:'NEW',
//... other properties
//dummy isNew function that would work on two properties to harden code
isNew:function(){
return (this.status=='NEW' || this.id == null);
}
});
angular.extend(this, data);
};
return Element;
});
正如其他帖子所述,Angular没有提供开箱即用的建模基类,但可以提供几个有用的函数:
用于与RESTful API交互和创建新对象的方法
建立模型之间的关系
在持久化到后端之前验证数据;对于显示实时错误也很有用
缓存和惰性加载以避免产生浪费的HTTP请求
状态机钩子(在保存、更新、创建、新建等之前/之后)
ngActiveResource (https://github.com/FacultyCreative/ngActiveResource)是一个能够很好地完成所有这些任务的库。完全公开——我编写了这个库——并且我已经成功地使用它构建了几个企业级应用程序。它经过了良好的测试,并提供了Rails开发人员应该熟悉的API。
我和我的团队继续积极地开发这个库,我希望看到更多的Angular开发人员为它做出贡献,并对它进行实战测试。
如果您希望多个控制器都可以使用服务,则应该使用服务。这里有一个简单的例子:
myApp.factory('ListService', function() {
var ListService = {};
var list = [];
ListService.getItem = function(index) { return list[index]; }
ListService.addItem = function(item) { list.push(item); }
ListService.removeItem = function(item) { list.splice(list.indexOf(item), 1) }
ListService.size = function() { return list.length; }
return ListService;
});
function Ctrl1($scope, ListService) {
//Can add/remove/get items from shared list
}
function Ctrl2($scope, ListService) {
//Can add/remove/get items from shared list
}
DCI is a paradigm and as such there's no angularJS way of doing it, either the language support DCI or it doesn't. JS support DCI rather well if you are willing to use source transformation and with some drawbacks if you are not. Again DCI has no more to do with dependency injection than say a C# class has and is definitely not a service either. So the best way to do DCI with angulusJS is to do DCI the JS way, which is pretty close to how DCI is formulated in the first place. Unless you do source transformation, you will not be able to do it fully since the role methods will be part of the object even outside the context but that's generally the problem with method injection based DCI. If you look at fullOO.info the authoritative site for DCI you could have a look at the ruby implementations they also use method injection or you could have a look at here for more information on DCI. It's mostly with RUby examples but the DCI stuff is agnostic to that.
One of the keys to DCI is that what the system does is separated from what the system is. So the data object are pretty dumb but once bound to a role in a context role methods make certain behaviour available. A role is simply an identifier, nothing more, an when accessing an object through that identifier then role methods are available. There's no role object/class. With method injection the scoping of role methods is not exactly as described but close. An example of a context in JS could be
function transfer(source,destination){
source.transfer = function(amount){
source.withdraw(amount);
source.log("withdrew " + amount);
destination.receive(amount);
};
destination.receive = function(amount){
destination.deposit(amount);
destination.log("deposited " + amount);
};
this.transfer = function(amount){
source.transfer(amount);
};
}