我正在为我的最新项目使用 AngularJS。在文档和教程中,所有模型数据都放入控制器范围内。我知道必须有控制器可用,因此必须在相应的视图中可用。
但是我认为该模型实际上不应该在那里实施。例如,它可能很复杂并且具有私有属性。此外,人们可能希望在另一个上下文/应用程序中重用它。将所有内容都放入控制器完全破坏了 MVC 模式。
这同样适用于任何模型的行为。如果我使用 DCI architecture 并将行为与数据模型分开,我将不得不引入额外的对象来保存行为。这将通过引入角色和上下文来完成。
DCI == 数据协作交互
当然,模型数据和行为可以用普通的 javascript 对象或任何“类”模式来实现。但是 AngularJS 的方法是什么?使用服务?
所以归结为这个问题:
遵循 AngularJS 最佳实践,您如何实现与控制器分离的模型?
如果您想要多个控制器可以使用的东西,您应该使用服务。这是一个简单的人为示例:
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):为了更清楚,我只使用模型类来分解简单的获取器/设置器(例如:用于视图模板)。对于大业务逻辑,我建议使用“了解”模型的单独服务,但要与它们分开,并且只包含业务逻辑。如果您愿意,可以将其称为“业务专家”服务层
service/ElementServices.js(注意元素是如何注入到声明中的)
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 工厂,用于创建对象)
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 对模型没有任何限制或要求。没有可继承的类或用于访问或更改模型的特殊访问器方法。模型可以是原始的、对象散列或完整的对象类型。简而言之,模型是一个普通的 JavaScript 对象。 — AngularJS 开发者指南 - V1.5 概念 - 模型
所以这意味着如何声明模型取决于您。这是一个简单的 Javascript 对象。
我个人不会使用 Angular 服务,因为它们的行为类似于你可以使用的单例对象,例如,在你的应用程序中保持全局状态。
DCI 是一种范式,因此没有 angularJS 方法可以做到这一点,无论语言支持还是不支持 DCI。如果您愿意使用源代码转换,那么 JS 对 DCI 的支持相当好,如果您不愿意,则有一些缺点。同样,DCI 与依赖注入没有更多关系,就像说 C# 类具有并且绝对不是服务一样。因此,使用 angulusJS 进行 DCI 的最佳方式是使用 JS 方式进行 DCI,这非常接近于最初制定 DCI 的方式。除非您进行源转换,否则您将无法完全做到这一点,因为即使在上下文之外,角色方法也将成为对象的一部分,但这通常是基于方法注入的 DCI 的问题。如果您查看 fullOO.info DCI 的权威站点,您可以查看它们也使用方法注入的 ruby 实现,或者您可以查看 here 以获取有关 DCI 的更多信息。它主要是 RUby 示例,但 DCI 的东西对此是不可知的。 DCI 的关键之一是系统所做的与系统是什么是分开的。因此,数据对象非常愚蠢,但一旦绑定到上下文中的角色,角色方法就会使某些行为可用。角色只是一个标识符,仅此而已,当通过该标识符访问对象时,角色方法可用。没有角色对象/类。使用方法注入,角色方法的作用域并不完全像描述的那样,而是接近的。 JS 中的上下文示例可能是
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);
};
}
这篇关于 AngularJS 模型的文章可能会有所帮助:
http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/
正如其他发帖人所说,Angular 没有提供开箱即用的建模基类,但可以提供以下几个有用的功能:
与 RESTful API 交互和创建新对象的方法 建立模型之间的关系 在持久化到后端之前验证数据;也可用于显示实时错误缓存和延迟加载以防止产生浪费的 HTTP 请求状态机挂钩(保存、更新、创建、新建等之前/之后)
一个能很好地完成所有这些事情的库是 ngActiveResource (https://github.com/FacultyCreative/ngActiveResource)。完全公开——我编写了这个库——我已经成功地使用它来构建几个企业级应用程序。它经过了很好的测试,并提供了 Rails 开发人员应该熟悉的 API。
我和我的团队继续积极开发这个库,我希望看到更多的 Angular 开发人员为它做出贡献并对其进行实战测试。
ngActiveResource
和 Angular 的 $resource
服务有什么区别。我对 Angular 有点陌生,并且很快浏览了两组文档,但它们似乎提供了很多重叠。 ngActiveResource
是在 $resource
服务可用之前开发的吗?
一个较老的问题,但鉴于 Angular 2.0 的新方向,我认为这个主题比以往任何时候都更相关。我想说一个最佳实践是编写对特定框架的依赖尽可能少的代码。仅使用可以增加直接价值的框架特定部分。
目前看来,Angular 服务是少数几个将成为下一代 Angular 的概念之一,因此遵循将所有逻辑转移到服务的一般准则可能是明智之举。但是,我认为即使不直接依赖 Angular 服务,您也可以制作解耦模型。创建仅具有必要依赖项和职责的自包含对象可能是要走的路。在进行自动化测试时,它也让生活变得更加轻松。如今,单一职责是一项热门工作,但它确实很有意义!
这是我认为有利于将对象模型与 dom 解耦的模式示例。
http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e
一个关键目标是以某种方式构建代码,使其在单元测试中与在视图中一样易于使用。如果你做到了,你就可以很好地编写现实和有用的测试。
我已尝试在 this blog post 中解决该确切问题。
基本上,数据建模的最佳场所是服务和工厂。但是,根据您检索数据的方式和您需要的行为的复杂性,有很多不同的方法可以实现。 Angular 目前没有标准方法或最佳实践。
这篇文章介绍了三种方法,使用 $http、$resource 和 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 Data Models: $http VS $resource VS Restangular
Angular 2.0 有可能为数据建模提供更强大的解决方案,让每个人都在同一个页面上。