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

331

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

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

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

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

Итак, дело доходит до этого вопроса:

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

  • 12
    Я бы проголосовал за этот вопрос, если бы вы могли определить DCI или хотя бы предоставить изложенную форму. Я никогда не видел эту аббревиатуру в литературе по программному обеспечению. Благодарю.
  • 13
    Я просто добавил ссылку на DCI в качестве ссылки.
Показать ещё 4 комментария
Теги:
model-view-controller
dci

8 ответов

156
Лучший ответ

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

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
    В том случае, если вам нужна одинаковая логика для нескольких контроллеров. Кроме того, таким образом проще тестировать вещи самостоятельно.
Показать ещё 9 комментариев
80

В настоящее время я пытаюсь использовать этот шаблон, который, хотя и не 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;
});
  • 4
    Я только вхожу в Angular, но мне было бы интересно узнать, почему ветераны сочтут это ересью. Это, вероятно, то, как я изначально подхожу к этому. Может ли кто-нибудь дать отзыв?
  • 2
    @Aaronius, просто чтобы прояснить: на самом деле я никогда не читал «вы никогда не должны этого делать» в любом документе или блоге angularjs, но я всегда читал что-то вроде «angularjs не нуждается в модели, он просто использует старый добрый javascript» и мне пришлось открыть этот паттерн самостоятельно. Так как это мой первый настоящий проект на AngularJS, я помещаю эти строгие предупреждения, чтобы люди не копировали / вставляли, не подумав сначала.
Показать ещё 8 комментариев
30

В документации Angularjs четко указано:

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

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

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

8

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

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

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

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

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

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

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

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

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

5

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

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

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

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

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

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

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

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

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

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

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

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', ['$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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню