Куда девать модельные данные и поведение? [tl; dr; Use Services]


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

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

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

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

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

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

8 335

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
}

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

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

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

EDIT (2): Чтобы быть еще более ясным, я использую класс модели только для факторизации простых геттеров / сеттеров (например : для использования в шаблонах представлений). Для большой бизнес-логики я рекомендую использовать отдельные службы, которые "знают" о модели, но хранятся отдельно из них и включают только бизнес-логику. Назовите его" бизнес-эксперт " уровень обслуживания, если вы хотите

сервис/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
}

модель/элемент.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 четко указано:

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

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

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

DCI-это парадигма, и как таковой нет способа angularJS сделать это, либо языковая поддержка DCI, либо нет. JS поддерживает DCI довольно хорошо, если вы готовы использовать преобразование источника и с некоторыми недостатками, если вы этого не делаете. Опять же, DCI не имеет никакого отношения к инъекции зависимостей, чем, скажем, класс C# имеет и определенно не является сервисом. Поэтому лучший способ сделать 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);
   };
}

эта статья о моделях в AngularJS может помочь:

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

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

  1. методы взаимодействия с RESTful API и создания новых объектов
  2. установление отношений между моделями
  3. проверка данных перед сохранением в бэкэнд; также полезно для отображения ошибок в реальном времени
  4. кэширование и ленивая загрузка, чтобы не делать расточительный HTTP запросы
  5. state machine hooks (до / после сохранения, обновления, создания, создания и т. д.)

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

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

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

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

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

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

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

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

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

сообщение охватывает три подхода, используя $http, $ resource и Restangular.

вот некоторые примеры кода для каждого, с пользовательским 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 (немного более запутанный):

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: $http VS $resource VS Restangular

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