Где разместить модель данных и поведения? [TL; др; Использовать услуги]

Я работаю с AngularJS для моего последнего проекта. В документации и руководствах все данные модели помещаются в область действия контроллера. Я понимаю, что это должно быть там, чтобы быть доступным для контроллера и, следовательно, в соответствующих представлениях.

Однако я не думаю, что модель должна быть реализована там. Это может быть сложно и иметь частные атрибуты, например. Кроме того, можно использовать его в другом контексте/приложении. Помещение всего в контроллер полностью нарушает схему MVC.

То же самое относится и к поведению любой модели. Если бы я использовал архитектуру DCI и отделил поведение от модели данных, мне пришлось бы вводить дополнительные объекты для хранения поведения. Это будет сделано путем введения ролей и контекстов.

DCI == Д ата С ollaboration я nteraction

Конечно, данные и поведение модели могут быть реализованы с помощью простых объектов JavaScript или любого шаблона "класса". Но каков будет AngularJS способ сделать это? Пользуетесь услугами?

Итак, все сводится к этому вопросу:

Как реализовать модели, отделенные от контроллера, следуя рекомендациям AngularJS?

Ответ 1

Вы должны использовать службы, если хотите что-то, используемое несколькими контроллерами. Вот простой надуманный пример:

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
}

Ответ 2

В настоящее время я пытаюсь использовать этот шаблон, который, хотя и не DCI, обеспечивает классическую развязку сервисов/моделей (с услугами для общения с веб-службами (например, модель CRUD) и моделью определения свойств и методов объекта).

Обратите внимание, что я использую этот шаблон только тогда, когда объекту модели нужны методы, работающие самостоятельно, которые я, вероятно, буду использовать везде (например, улучшенные getter/seters). Я не, выступая за то, чтобы делать это для каждой службы систематически.

EDIT: Раньше я думал, что этот шаблон будет идти против модели "Angular - простой старый объект javascript", но теперь мне кажется, что этот шаблон отлично подходит.

РЕДАКТИРОВАТЬ (2): Чтобы быть яснее, я использую класс Model только для того, чтобы разделить простые геттеры/сеттеры (например: для использования в виде шаблонов). Для бизнес-логики я рекомендую использовать отдельные службы, которые "знают" о модели, но сохраняются отдельно от них и включают только бизнес-логику. Назовите это словом "бизнес-эксперт", если вы хотите

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

Ответ 3

Документация Angularjs четко гласит:

В отличие от многих других фреймворков, Angular не предъявляет никаких ограничений или требований к модели. Нет классов для наследования или специальных методов доступа для доступа или изменения модели. Модель может быть примитивом, хэшем объекта или полным типом объекта. Короче говоря, модель представляет собой простой объект JavaScript.

- Руководство разработчика AngularJS - Концепции V1.5 - Модель

Значит, это зависит от вас, как объявить модель. Это простой объект Javascript.

Я лично не буду использовать Angular Services, поскольку они должны были вести себя как одноэлементные объекты, которые вы можете использовать, например, для сохранения глобальных состояний в вашем приложении.

Ответ 4

DCI - это парадигма, и поэтому нет никакого углового способа сделать это, либо поддержка языка DCI, либо нет. JS поддерживает DCI довольно хорошо, если вы готовы использовать преобразование источника и с некоторыми недостатками, если вы этого не делаете. Снова DCI больше не связан с инъекцией зависимостей, чем говорит класс С# и, безусловно, не является сервисом. Таким образом, лучший способ сделать DCI с помощью angulusJS - это сделать DCI способом JS, который довольно близок к тому, как DCI формулируется в первую очередь. Если вы не используете преобразование источника, вы не сможете сделать это полностью, поскольку методы ролей будут частью объекта даже вне контекста, но, как правило, проблема с DCI на основе метода. Если вы посмотрите fullOO.info авторитетный сайт для DCI, вы можете взглянуть на реализации 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);
   };
}

Ответ 6

Как указано другими плакатами, Angular не предоставляет базового класса для моделирования, но можно с пользой предоставить несколько функций:

  • Способы взаимодействия с API RESTful и создания новых объектов
  • Установление отношений между моделями
  • Проверка данных перед сохранением бэкэнд; также полезно для отображения ошибок в реальном времени.
  • Кэширование и ленивая загрузка, чтобы не доставлять расточительные HTTP-запросы.
  • Перехватывает состояние машины (до/после сохранения, обновления, создания, нового и т.д.)

Одна библиотека, которая хорошо все это делает, - ngActiveResource (https://github.com/FacultyCreative/ngActiveResource). Полное раскрытие - я написал эту библиотеку, и я успешно использовал ее для создания нескольких приложений масштаба предприятия. Он хорошо протестирован и предоставляет API, который должен быть знаком с разработчиками Rails.

Моя команда и я продолжаем активно развивать эту библиотеку, и мне бы хотелось, чтобы больше разработчиков Angular вносили вклад в нее и сражались с ней.

Ответ 7

Более старый вопрос, но я думаю, что тема более актуальна, чем когда-либо, учитывая новое направление Angular 2.0. Я бы сказал, что лучше всего написать код с минимальными зависимостями от конкретной структуры, насколько это возможно. Используйте только конкретные части фреймворка, где он добавляет прямое значение.

В настоящее время кажется, что служба Angular является одной из немногих концепций, которая сделает ее следующим поколением Angular, поэтому, вероятно, разумно следовать общему руководству по перемещению всей логики в службы. Тем не менее, я бы сказал, что вы можете создавать развязанные модели даже без прямой зависимости от сервисов Angular. Вероятно, путь к созданию самостоятельных объектов с необходимыми зависимостями и обязанностями. Это также облегчает жизнь при автоматическом тестировании. Единая ответственность - это шумная работа в наши дни, но она имеет большой смысл!

Вот пример patter, который я считаю хорошим для развязки объектной модели из dom.

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

Основная цель состоит в том, чтобы структурировать ваш код таким образом, чтобы он был так же прост в использовании из модульных тестов, как из представления. Если вы достигнете этого, у вас есть хорошие возможности для написания реалистичных и полезных тестов.

Ответ 8

Я попытался решить эту проблему в этом посте.

По сути, лучший дом для моделирования данных - это сервисы и фабрики. Однако, в зависимости от того, как вы извлекаете ваши данные, и от сложности поведения, которое вам нужно, есть много разных способов реализации. Angular в настоящее время не имеет стандартного способа или наилучшей практики.

В статье рассматриваются три подхода: использование $ http, $ resource и Restangular.

Вот пример кода для каждого из них с пользовательским getResult() в модели Job:

Рестаугольный (легкий горох):

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

$ ресурс (немного более замысловатый):

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 предложит более надежное решение для моделирования данных, которое позволит всем на одной странице.