我正在为我的最新项目使用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);
   };
}

其他回答

这是一个老问题了,但我认为这个话题在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 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目前没有标准方法或最佳实践。

这篇文章涵盖了三种方法,使用$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数据模型:$http VS $resource VS Restangular

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

Angularjs文档清楚地指出:

不像许多其他框架,Angular没有限制或 对模型的需求。没有要继承的类或 用于访问或更改模型的特殊访问器方法。的 model可以是原语、对象散列或完整的对象类型。简而言之 模型是一个简单的JavaScript对象。 - AngularJS开发者指南- V1.5概念-模型

因此,这意味着如何声明模型取决于您。 它是一个简单的Javascript对象。

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