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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11112608/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 04:31:07  来源:igfitidea点击:

Where to put model data and behaviour? [tl; dr; Use Services]

javascriptmodel-view-controllerangularjsdci

提问by Nils Blum-Oeste

I am working with AngularJS for my latest project. In the documentation and tutorials all model data is put into the controller scope. I understand that is has to be there to be available for the controller and thus within the corresponding views.

我正在为我的最新项目使用 AngularJS。在文档和教程中,所有模型数据都放入控制器范围内。我知道必须在那里可供控制器使用,从而在相应的视图中使用。

However I dont think the model should actually be implemented there. It might be complex and have private attributes for example. Furthermore one might want to reuse it in another context/app. Putting everything into the controller totally breaks MVC pattern.

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

The same holds true for the behaviour of any model. If I would use DCI architectureand separate behaviour from the data model, I would have to introduce additional objects to hold the behaviour. This would be done by introducing roles and contexts.

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

DCI == Data Collaboration Interaction

DCI == dATA Çollaborationnteraction

Of course model data and behaviour could be implemented with plain javascript objects or any "class" pattern. But what would be the AngularJS way to do it? Using services?

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

So it comes down to this question:

所以归结为这个问题:

How do you implement models decoupled from the controller, following AngularJS best practices?

您如何按照 AngularJS 最佳实践实现与控制器分离的模型?

采纳答案by Andrew Joslin

You should use services if you want something usable by multiple controllers. Here's a simple contrived example:

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

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
}

回答by Ben G

I'm currently trying this pattern, which, although not DCI, provides a classical service / model decoupling (with services for talking to web services (aka model CRUD), and model defining the object properties and methods).

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

Note that i only use this pattern whenever the model object needs methods working on its ownproperties, that i'll probably use everywhere (such as improved getter/setters). I'm notadvocating doing this for every service systematically.

请注意,我仅在模型对象需要处理其自身属性的方法时才使用此模式,我可能会在任何地方使用(例如改进的 getter/setter)。我并不是提倡系统地为每项服务都这样做。

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.

编辑:我曾经认为这种模式会违背“Angular 模型是普通的旧 javascript 对象”的口头禅,但现在在我看来,这种模式非常好。

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

编辑(2):为了更清楚,我只使用模型类来分解简单的getter/setter(例如:在视图模板中使用)。对于大型业务逻辑,我建议使用单独的服务,这些服务“了解”模型,但与它们保持分离,并且仅包含业务逻辑。如果您愿意,可以将其称为“业务专家”服务层

service/ElementServices.js(notice how Element is injected in the declaration)

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(using angularjs Factory, made for object creation)

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

回答by S.C.

The Angularjs documentation clearly states:

Angularjs 文档明确指出:

Unlike many other frameworks Angular makes no restrictions or requirements on the model. There are no classes to inherit from or special accessor methods for accessing or changing the model. The model can be primitive, object hash, or a full object Type. In short the model is a plain JavaScript object.

AngularJS Developer Guide - V1.5 Concepts - Model

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

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

So it means that's up to you how to declare a model. It's a simple Javascript object.

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

I personally won't use Angular Services as they were meant to behave like singleton objects you can use, for example, to keep global states across your application.

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

回答by Rune FS

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.infothe 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 herefor 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

DCI 是一种范式,因此没有 angularJS 的做法,要么语言支持 DCI,要么不支持。如果您愿意使用源代码转换,JS 会很好地支持 DCI,如果您不愿意,则有一些缺点。同样,DCI 与依赖注入没有更多关系,就像说 C# 类已经并且绝对不是服务一样。因此,使用 angulusJS 进行 DCI 的最佳方法是以 JS 的方式进行 DCI,这与最初制定 DCI 的方式非常接近。除非您进行源转换,否则您将无法完全完成,因为即使在上下文之外,角色方法也将成为对象的一部分,但这通常是基于方法注入的 DCI 的问题。如果你看fullOO.infoDCI 的权威站点,您可以查看他们也使用方法注入的 ruby​​ 实现,或者您可以查看此处了解有关 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);
   };
}

回答by marianboda

回答by Brett Cassette

As stated by other posters, Angular provides no out-of-the-box base class for modeling, but one can usefully provide several functions:

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

  1. Methods for interacting with a RESTful API and creating new objects
  2. Establishing relationships between models
  3. Validating data before persisting to the backend; also useful for displaying real-time errors
  4. Caching and lazy-loading to keep from making wasteful HTTP requests
  5. State machine hooks (before/after save, update, create, new, etc)
  1. 与 RESTful API 交互和创建新对象的方法
  2. 建立模型之间的关系
  3. 在持久化到后端之前验证数据;也可用于显示实时错误
  4. 缓存和延迟加载以防止产生浪费的 HTTP 请求
  5. 状态机挂钩(保存、更新、创建、新建等之前/之后)

One library that does all of these things well is ngActiveResource (https://github.com/FacultyCreative/ngActiveResource). Full disclosure--I wrote this library--and I have used it successfully in building several enterprise-scale applications. It's well tested, and provides an API that should be familiar to Rails developers.

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

My team and I continue to actively develop this library, and I'd love to see more Angular developers contribute to it and battle test it.

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

回答by TGH

An older question, but I think the topic is more relevant than ever given the new direction of Angular 2.0. I would say a best practice is to write code with as few dependencies on a particular framework as possible. Only use the framework specific parts where it adds direct value.

一个较旧的问题,但我认为考虑到 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!

目前,Angular 服务似乎是少数几个将其引入下一代 Angular 的概念之一,因此遵循将所有逻辑移至服务的一般准则可能是明智之举。但是,我认为即使不直接依赖 Angular 服务,您也可以制作解耦模型。创建仅具有必要依赖项和职责的自包含对象可能是要走的路。在进行自动化测试时,它还使生活变得更加轻松。如今,单一职责是一项热门工作,但它确实很有意义!

Here is an example of a patter I consider good for decoupling the object model from the dom.

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

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

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

A key goal is to structure your code in a way that makes it just as easy to use from a unit tests as from a view. If you achieve that you are well positioned to write realistic and useful tests.

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

回答by Alan Christopher Thomas

I've tried to tackle that exact issue in this blog post.

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

Basically, the best home for data modeling is in services and factories. However, depending on how you retrieve your data and the complexity of the behaviors you need, there are lots of different ways to go about the implementation. Angular currently has no standardway or best practice.

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

The post covers three approaches, using $http, $resource, and Restangular.

这篇文章涵盖了三种方法,使用$http$resourceRestangular

Here's some example code for each, with a custom getResult()method on the Job model:

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

Restangular (easy peasy):

Restangular(简单的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):

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

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

The blog post itself goes into more detail on the reasoning behind why you might use each approach, as well as code examples of how to use the models in your controllers:

博客文章本身更详细地介绍了为什么使用每种方法的原因,以及如何在控制器中使用模型的代码示例:

AngularJS Data Models: $http VS $resource VS Restangular

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

There's a possibility Angular 2.0 will offer a more robust solution to data modeling that gets everyone on the same page.

Angular 2.0 有可能为数据建模提供更强大的解决方案,让每个人都在同一页面上。