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

341

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

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

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

DCI == Д ата С ollaboration я nteraction

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

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

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

Нильс Блум-Оэсте
источник
12
Я бы проголосовал за этот вопрос, если бы вы могли определить DCI или хотя бы предоставить изложенную форму. Я никогда не видел эту аббревиатуру в литературе по программному обеспечению. Спасибо.
Джим Раден
13
Я просто добавил ссылку на DCI в качестве ссылки.
Нильс Блум-Оэсте
1
@JimRaden DCI - это Dataq, Context, взаимодействие и парадигма, впервые сформулированная отцом MVC (Trygve Reenskauge). На данный момент есть довольно много литературы по этому вопросу. Хорошее чтение - Коплен и Бьёрнвиг «Бережливая архитектура»
Rune FS
3
Спасибо. Хорошо это или плохо, но большинство людей даже не знают об оригинальной литературе. По данным Google, существует 55 миллионов статей о MVC, но только 250 000 упоминают MCI и MVC. А на Microsoft.com? 7. AngularJS.org даже не упоминает аббревиатуру DCI: «Ваш поиск - site: angularjs.org dci - не соответствует ни одному документу».
Джим Раден
Ресурсные объекты - это в основном модели в Angular.js .. Я их расширяю.
Салман фон Аббас

Ответы:

155

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

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
}
Эндрю Джослин
источник
23
Какая польза от использования сервиса по сравнению с простым созданием простого объекта Javascript в качестве модели и присвоением его области действия контроллера?
Нильс Блум-Оэсте
22
В случае, если вам нужна одинаковая логика для нескольких контроллеров. Кроме того, таким образом легче тестировать вещи самостоятельно.
Эндрю Джослин
1
Последний пример отстой, этот имеет больше смысла. Я редактировал это.
Эндрю Джослин
9
Да, с простым старым объектом Javascript вы не сможете внедрить что-нибудь Angular в свой ListService. Как и в этом примере, если вам нужно было $ http.get для получения данных списка в начале, или если вам нужно было вставить $ rootScope, чтобы вы могли $ широковещательные события.
Эндрю Джослин
1
Чтобы сделать этот пример более похожим на DCI, не должны ли данные находиться за пределами ListService?
PiTheNumber
81

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

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

РЕДАКТИРОВАТЬ: Раньше я думал, что этот шаблон будет идти против мантры «Угловая модель - это простой старый объект 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;
});
Бен Г
источник
4
Я только вхожу в Angular, но мне было бы любопытно узнать, почему ветераны сочтут это ересью. Это, вероятно, то, как я бы изначально подошел к этому. Может ли кто-нибудь дать отзыв?
Аароний,
2
@Aaronius, просто чтобы прояснить: на самом деле я никогда не читал «вы никогда не должны этого делать» в любом документе или блоге angularjs, но я всегда читал что-то вроде «angularjs не нуждается в модели, он просто использует старый добрый javascript» и мне пришлось открыть этот паттерн самостоятельно. Так как это мой первый настоящий проект на AngularJS, я помещаю эти серьезные предупреждения, чтобы люди не копировали / вставляли, не подумав сначала.
Бен Г
Я остановился на примерно такой же схеме. Жаль, что у Angular нет реальной поддержки (или, казалось бы, желания поддерживать) модели в «классическом» смысле.
Drt
3
Это не выглядит ересью для меня, вы используете фабрики для того, для чего они были созданы: для создания объектов. Я считаю, что фраза «angularjs не нуждается в модели» означает «вам не нужно наследовать от специального класса или использовать специальные методы (например, ko.observable, в нокауте) для работы с моделями в angular, a чисто js объекта будет достаточно ".
Фелипе Кастро
1
Не приведет ли наличие соответствующего элемента ElementService для каждой коллекции к множеству почти идентичных файлов?
Коллин Аллен
29

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

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

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

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

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

Южная Каролина
источник
Вы должны предоставить ссылку, где это указано в документации. Я сделал в Google поиск «Angular не устанавливает никаких ограничений или требований к модели» , и, насколько я могу судить, он нигде не встречается в официальных документах.
4
это было в старых документах angularjs (которые были живы во время ответа): github.com/gitsome/docular/blob/master/lib/angular/ngdocs/guide/…
SC
8

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);
   };
}
Руна ФС
источник
1
Спасибо за разработку материала DCI. Это отличное чтение. Но мои вопросы на самом деле направлены на то, «где поместить объекты модели в angularjs». DCI только для справки, так что я мог бы не только иметь модель, но и разделить ее по DCI. Отредактирую вопрос, чтобы сделать его более понятным.
Нильс Блум-Оэсте
7

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

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

marianboda
источник
7
Обратите внимание, что ответы , содержащие только ссылки, не приветствуются, поэтому ответы SO должны быть конечной точкой поиска решения (в отличие от еще одной остановки ссылок, которая, как правило, устаревает со временем). Пожалуйста, рассмотрите возможность добавления отдельного краткого обзора здесь, сохраняя ссылку в качестве ссылки.
Клеопатра
добавление такой ссылки в комментарии к вопросу было бы хорошо, хотя.
Джорребор
Эта ссылка на самом деле очень хорошая статья, но то же самое нужно было бы включить в ответ, чтобы быть подходящим для SO
Джереми Зерр
5

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

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

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

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

Бретт Кассет
источник
Привет! Это действительно здорово! Я включу его в свое приложение прямо сейчас. Боевые испытания только начались.
Дж. Бруни
1
Я просто смотрю на ваш пост и задаюсь вопросом, в чем разница между вашим сервисом ngActiveResourceи $resourceсервисом Angular . Я немного новичок в Angular и быстро просмотрел оба набора документов, но они, кажется, предлагают много общего. Был ngActiveResourceразработан до того, как $resourceуслуга будет доступна?
Эрик Б.
5

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

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

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

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

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

TGH
источник
4

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

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

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

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

Restangle (легкий горох):

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 Ограниченный

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

Алан Кристофер Томас
источник