在哪里放置模型数据和行为?[tl;博士;使用服务)

我正在为我的最新项目使用AngularJS。在文档和教程中,所有模型数据都放在控制器范围内。我理解它必须在那里为控制器可用,因此在相应的视图中。

然而,我不认为这个模型应该在那里实现。例如,它可能很复杂并具有私有属性。此外,人们可能想在另一个上下文/应用程序中重用它。把所有东西都放到控制器中完全打破了MVC模式。

这同样适用于任何模型的行为。如果我要使用DCI架构并将行为与数据模型分离,我将不得不引入额外的对象来保存行为。这可以通过引入角色和上下文来实现。

DCI == Data __abc1collaboration __abc2interaction

当然,模型数据和行为可以用简单的javascript对象或任何“类”模式来实现。但是AngularJS会怎么做呢?使用服务?

所以这就归结为一个问题:

如何实现与控制器解耦的模型,遵循AngularJS的最佳实践?

75657 次浏览
DCI是一种范式,因此angularJS没有办法做到这一点,无论语言是否支持DCI。如果你愿意使用源转换,JS会很好地支持DCI,如果你不愿意,则会有一些缺点。同样,DCI与依赖注入的关系并不比c#类更大,也绝对不是服务。所以用angulusJS做DCI的最好方法是用JS的方式做DCI,这非常接近DCI最初的表述方式。除非你做源转换,否则你将无法完全完成它,因为角色方法即使在上下文之外也是对象的一部分,但这通常是基于方法注入的DCI的问题。如果你看看DCI的权威网站fullOO.info,你可以看看ruby实现,他们也使用方法注入,或者你可以看看在这里,以获得更多关于DCI的信息。它主要是用RUby的例子,但DCI的东西是不可知论的。 DCI的关键之一是系统所做的与系统本身是分开的。所以数据对象很笨,但一旦绑定到上下文中的角色,角色方法就会使某些行为可用。角色只是一个标识符,当通过标识符访问对象时,角色方法是可用的。没有角色对象/类。使用方法注入,角色方法的作用域并不完全像描述的那样,但很接近。JS中的context的一个例子可以是

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

如果您希望多个控制器都可以使用服务,则应该使用服务。这里有一个简单的例子:

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)。我主张为每个服务系统地这样做。

< p > <强>编辑: 我曾经认为这种模式违背了“Angular模型是普通的javascript对象”的信条,但现在对我来说,这种模式完全没问题 < p > <强>编辑(2): 更清楚地说,我使用Model类只分解简单的getter / setter(例如:在视图模板中使用)。对于大的业务逻辑,我建议使用单独的服务,这些服务“知道”模型,但与模型保持分离,并且只包括业务逻辑。如果您希望

,则将其称为“业务专家”服务层

服务/ 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
}

模型/ 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;
});

Angularjs文档清楚地指出:

与许多其他框架不同,Angular没有限制或 对模型的需求。没有要继承的类或 用于访问或更改模型的特殊访问器方法。的 model可以是原语、对象散列或完整的对象类型。简而言之 模型是一个简单的JavaScript对象

——AngularJS开发者指南- V1.5概念-模型

所以这意味着这取决于你如何声明一个模型。 这是一个简单的Javascript对象

我个人不会使用Angular服务,因为它们的行为就像你可以使用的单例对象,例如,在应用程序中保持全局状态。

正如其他帖子所述,Angular没有提供开箱即用的建模基类,但可以提供几个有用的函数:

  1. 用于与RESTful API交互和创建新对象的方法
  2. 建立模型之间的关系
  3. 在持久化到后端之前验证数据;对于显示实时错误也很有用
  4. 缓存和惰性加载以避免产生浪费的HTTP请求
  5. 状态机钩子(在保存、更新、创建、新建等之前/之后)

ngActiveResource (https://github.com/FacultyCreative/ngActiveResource)是一个能很好地完成所有这些事情的库。完全公开——我编写了这个库——并且我已经成功地使用它构建了几个企业级应用程序。它经过了良好的测试,并提供了Rails开发人员应该熟悉的API。

我和我的团队继续积极地开发这个库,我希望看到更多的Angular开发人员为它做出贡献,并对它进行实战测试。

我已经尝试在这篇博文中解决这个确切的问题。

基本上,数据建模的最佳场所是服务和工厂。但是,根据检索数据的方式和所需行为的复杂性,有许多不同的实现方法。Angular目前没有标准方法或最佳实践。

这篇文章涵盖了三种方法,使用http美元美元的资源Restangular

下面是它们的一些示例代码,在Job模型上有一个自定义的getResult()方法:

Restangular(简单):

angular.module('job.models', [])
.service('Job', ['Restangular', function(Restangular) {
var Job = Restangular.service('jobs');


Restangular.extendModel('jobs', function(model) {
model.getResult = function() {
if (this.status == 'complete') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};


return model;
});


return Job;
}]);

$resource(稍微复杂一点):

angular.module('job.models', [])
.factory('Job', ['$resource', function($resource) {
var Job = $resource('/api/jobs/:jobId', { full: 'true', jobId: '@id' }, {
query: {
method: 'GET',
isArray: false,
transformResponse: function(data, header) {
var wrapped = angular.fromJson(data);
angular.forEach(wrapped.items, function(item, idx) {
wrapped.items[idx] = new Job(item);
});
return wrapped;
}
}
});


Job.prototype.getResult = function() {
if (this.status == 'complete') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};


return Job;
}]);

美元的http(核心):

angular.module('job.models', [])
.service('JobManager', ['$http', 'Job', function($http, Job) {
return {
getAll: function(limit) {
var params = {"limit": limit, "full": 'true'};
return $http.get('/api/jobs', {params: params})
.then(function(response) {
var data = response.data;
var jobs = [];
for (var i = 0; i < data.objects.length; i ++) {
jobs.push(new Job(data.objects[i]));
}
return jobs;
});
}
};
}])
.factory('Job', function() {
function Job(data) {
for (attr in data) {
if (data.hasOwnProperty(attr))
this[attr] = data[attr];
}
}


Job.prototype.getResult = function() {
if (this.status == 'complete') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};


return Job;
});

这篇博文本身更详细地解释了为什么你可能会使用每种方法,以及如何在你的控制器中使用模型的代码示例:

AngularJS数据模型:$http VS $resource VS Restangular

Angular 2.0可能会提供一个更健壮的数据建模解决方案,让所有人都能达成共识。

这是一个老问题了,但我认为这个话题在Angular 2.0的新方向下比以往任何时候都更有意义。我认为最好的做法是编写对特定框架的依赖越少越好。只使用框架特定的可以增加直接价值的部分。

目前看来,Angular的服务是少数几个会出现在下一代Angular中的概念之一,所以遵循将所有逻辑迁移到服务的一般原则可能是明智的。然而,我认为你可以在不直接依赖Angular服务的情况下创建解耦模型。创建只包含必要依赖项和职责的自包含对象可能是正确的方法。在进行自动化测试时,它也使工作变得更加容易。如今,单一责任是一项热门工作,但它确实很有意义!

下面是一个范例,我认为它可以很好地将对象模型与dom解耦。

http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e < a href = " http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e " > < / >

一个关键的目标是构建代码,使其在单元测试中和在视图中一样易于使用。如果您做到了这一点,那么您就可以很好地编写现实而有用的测试了。