首页 文章

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

提问于
浏览
335

我正在与AngularJS合作完成我的最新项目 . 在文档和教程中,所有模型数据都放入控制器范围 . 我知道必须存在控制器,因此在相应的视图中 .

但是,我不认为该模型应该在那里实施 . 它可能很复杂并且具有私有属性 . 此外,人们可能希望在另一个上下文/应用程序中重用它 . 将所有内容放入控制器完全打破了MVC模式 .

对于任何模型的行为都是如此 . 如果我使用DCI architecture并从数据模型中分离出行为,我将不得不引入其他对象来保存行为 . 这可以通过引入角色和上下文来完成 .

当然,模型数据和行为可以使用普通的javascript对象或任何“类”模式来实现 . 但AngularJS的做法是什么呢?使用服务?

So it comes down to this question:

在AngularJS最佳实践之后,您如何实现与控制器分离的模型?

8 回答

  • 8

    如果您想要多个控制器可以使用的东西,您应该使用服务 . 这是一个简单的人为例子:

    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
    }
    
  • 7

    我目前正在尝试这种模式,虽然不是DCI,但它提供了一种经典的服务/模型解耦(用于与Web服务交谈的服务(又称模型CRUD),以及定义对象属性和方法的模型) .

    请注意,每当模型对象需要处理 on its own 属性的方法时,我才会使用此模式,即提倡为每个服务系统地执行此操作 .

    EDIT: I used to think this pattern would go against the "Angular model is plain old javascript object" mantra, but it seems to me now that this pattern is perfectly fine.

    EDIT (2): To be even clearer, I use a Model class only to factor simple getters / setters (e.g. : to be used in view templates). For big business logic, i recommend using separate service(s) that "know" about the model, but are kept separated from them, and only include business logic. Call it a "business expert" service layer if you want

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

    Angularjs文件明确指出:

    与许多其他框架不同,Angular对模型没有任何限制或要求 . 没有要继承的类或用于访问或更改模型的特殊访问器方法 . 模型可以是原始,对象哈希或完整对象类型 . 简而言之,该模型是一个普通的JavaScript对象 .

    因此,这意味着由您决定如何声明模型 . 这是一个简单的Javascript对象 .

    我个人不会使用Angular Services,因为它们的行为类似于您可以使用的单例对象,例如,在您的应用程序中保持全局状态 .

  • 30

    DCI是一种范式,因此有一个范例 . 如果你愿意使用源代码转换,JS会很好地支持DCI,如果你不愿意,则会有一些缺点 . DCI再次与依赖注入无关,而不是说C#类具有并且绝对不是服务 . 因此,使用angulusJS进行DCI的最佳方法是使用JS方式进行DCI,这与DCI首先制定的方式非常接近 . 除非您进行源转换,否则您将无法完全执行此操作,因为即使在上下文之外,角色方法也将成为对象的一部分,但这通常是基于方法注入的DCI的问题 . 如果您查看DCI的权威站点fullOO.info,您可以查看ruby实现,他们也使用方法注入,或者您可以查看here以获取有关DCI的更多信息 . 它'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'没有角色对象/类 . 使用方法注入时,角色方法的范围并不完全如描述但接近 . 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);
       };
    }
    
  • 5

    这篇关于AngularJS中的模型的文章可能会有所帮助:

    http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/

  • 5

    正如其他海报所述,Angular没有为建模提供开箱即用的基类,但可以有用地提供几个功能:

    • 与RESTful API交互并创建新对象的方法

    • Build 模型之间的关系

    • 在持久化到后端之前验证数据;也可用于显示实时错误

    • 缓存和延迟加载,以避免浪费HTTP请求

    • 状态机挂钩(保存,更新,创建,新建等之前/之后)

    一个完成所有这些工作的库是ngActiveResource(https://github.com/FacultyCreative/ngActiveResource) . 完全披露 - 我写了这个库 - 我已成功地用它来构建几个企业级应用程序 . 它经过了充分测试,并提供了Rails开发人员应该熟悉的API .

    我和我的团队继续积极开发这个库,我很想看到更多Angular开发人员为它做出贡献战斗测试吧 .

  • 4

    一个较老的问题,但我认为鉴于Angular 2.0的新方向,该主题比以往任何时候都更具相关性 . 我想说最好的做法是编写尽可能少的依赖于特定框架的代码 . 仅使用特定于框架的部分来增加直接 Value .

    目前似乎Angular服务是为少数几个概念提供给下一代Angular的概念之一,因此遵循将所有逻辑转移到服务的一般准则可能是明智的 . 但是,我认为即使没有直接依赖Angular服务,也可以制作解耦模型 . 创建只包含必要依赖关系和责任的自包含对象可能是最佳选择 . 它还可以在进行自动化测试时使生活更轻松 . 如今,单一责任是一项嗡嗡声的工作,但它确实很有意义!

    这是一个我认为适合将对象模型与dom分离的模式示例 .

    http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e

    一个关键的目标是以一种方式构建代码,使其从单元测试和视图一样容易使用 . 如果您实现了这一目标,那么您就可以编写实际且有用的测试 .

  • 152

    我试图在this blog post中解决这个问题 .

    基本上,数据建模的最佳家园是服务和工厂 . 但是,根据您检索数据的方式以及所需行为的复杂程度,有很多不同的方法可以实现 . Angular目前没有标准方法或最佳实践 .

    该帖子涵盖了三种方法,使用 $http$resourceRestangular .

    以下是每个示例代码,在作业模型上使用自定义 getResult() 方法:

    Restangular (easy peasy):

    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 (slightly more convoluted):

    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 (hardcore):

    angular.module('job.models', [])
        .service('JobManager', ['$q', '$http', 'Job', function($q, $http, Job) {
            return {
                getAll: function(limit) {
                    var deferred = $q.defer();
    
                    $http.get('/api/jobs?limit=' + limit + '&full=true').success(function(data) {
                        var jobs = [];
                        for (var i = 0; i < data.objects.length; i ++) {
                            jobs.push(new Job(data.objects[i]));
                        }
                        deferred.resolve(jobs);
                    });
    
                    return deferred.promise;
                }
            };
        }])
        .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有可能为数据建模提供更强大的解决方案,使每个人都在同一页面上 .

相关问题