我正在为我的最新项目使用AngularJS。在文档和教程中,所有模型数据都放在控制器范围内。我理解它必须在那里为控制器可用,因此在相应的视图中。
然而,我不认为这个模型应该在那里实现。例如,它可能很复杂并具有私有属性。此外,人们可能想在另一个上下文/应用程序中重用它。把所有东西都放到控制器中完全打破了MVC模式。
这同样适用于任何模型的行为。如果我要使用DCI体系结构并将行为与数据模型分离,我将不得不引入额外的对象来保存行为。这可以通过引入角色和上下文来实现。
DCI ==数据协作交互
当然,模型数据和行为可以用简单的javascript对象或任何“类”模式来实现。但是AngularJS会怎么做呢?使用服务?
所以这就归结为一个问题:
如何实现与控制器解耦的模型,遵循AngularJS的最佳实践?
我目前正在尝试这种模式,虽然不是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;
});
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);
};
}
这是一个老问题了,但我认为这个话题在Angular 2.0的新方向下比以往任何时候都更有意义。我认为最好的做法是编写对特定框架的依赖越少越好。只使用框架特定的可以增加直接价值的部分。
Currently it seems like the Angular service is one of the few concepts that will make it to the next generation of Angular, so it's probably smart to follow the general guideline of moving all logic to services. However, I would argue that you can make decoupled models even without a direct dependency on Angular services. Creating self contained objects with only necessary dependencies and responsibilities is probably the way to go. It also makes life a lot easier when doing automated testing. Single responsibility is a buzz work these days, but it does make a lot of sense!
下面是一个范例,我认为它可以很好地将对象模型与dom解耦。
http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e
一个关键的目标是构建代码,使其在单元测试中和在视图中一样易于使用。如果您做到了这一点,那么您就可以很好地编写现实而有用的测试了。
如果您希望多个控制器都可以使用服务,则应该使用服务。这里有一个简单的例子:
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,但它提供了一个经典的服务/模型解耦(与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;
});