Куди розмістити дані моделі та поведінку? [тл; лікар; Використовувати послуги]


341

Я працюю з AngularJS над своїм останнім проектом. У документації та навчальних посібниках всі дані моделі вкладаються в область контролера. Я розумію, що це повинно бути доступним для контролера і, таким чином, у відповідних думках.

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

Те саме стосується поведінки будь-якої моделі. Якби я використовував архітектуру DCI і відокремлював поведінку від моделі даних, я повинен був би ввести додаткові об'єкти для утримання поведінки. Це можна зробити шляхом введення ролей та контекстів.

DCI == Д ата З ollaboration я nteraction

Звичайно, дані моделі та поведінка можуть бути реалізовані з простими об'єктами javascript або будь-яким шаблоном "класу". Але яким був би спосіб AngularJS це зробити? Використовуєте послуги?

Тож зводиться до цього питання:

Як ви реалізуєте моделі, відірвані від контролера, дотримуючись кращих практик AngularJS?


12
Я б голосував за це питання, якщо ви могли б визначити DCI або хоча б надати прописану форму. Я ніколи не бачив цієї абревіатури в жодній програмній літературі. Дякую.
Джим Раден

13
Я щойно додав посилання на DCI в якості посилання.
Nils Blum-Oeste

1
@JimRaden DCI - це Dataq, контекст, взаємодія та парадигма, сформульована спочатку батьком MVC (Trygve Reenskauge). На сьогоднішній день у цій тематиці існує досить багато літератури. Гарне прочитання - Коплін та Бьорнвіг "Ощадна архітектура"
Rune FS

3
Дякую. На краще чи гірше, більшість людей досі навіть не знає про оригінальну літературу. За даними Google, є 55 мільйонів статей про MVC, але лише 250 000, де згадуються MCI та MVC. А на Microsoft.com? 7. AngularJS.org навіть не згадує абревіатуру DCI: "Ваш пошук - сайт: 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 як моделі та присвоєнням цього області контролера?
Nils Blum-Oeste

22
Якщо вам потрібна однакова логіка, що ділиться між кількома контролерами. Також таким чином простіше перевірити речі самостійно.
Ендрю Джослін

1
Останній вид прикладу висмоктаний, цей має більше сенсу. Я її відредагував.
Ендрю Джослін

9
Так, із простим старим об'єктом Javascript ви не зможете ввести щось кутове у ваш ListService. Як і в цьому прикладі, якщо вам потрібно було $ http.get, щоб отримати дані зі списку на початку, або якщо вам потрібно було ввести $ rootScope, щоб ви могли $ трансляцію подій.
Ендрю Джослін

1
Щоб зробити цей приклад більш DCI, як, чи не повинні дані знаходитись поза ListService?
PiTheNumber

81

Наразі я намагаюся цю схему, яка, хоча і не DCI, забезпечує класичну розв’язку послуги / моделі (з послугами для спілкування з веб-службами (він же модель CRUD) та модель, що визначає властивості та методи об'єкта).

Зауважте, що я використовую цей шаблон лише тоді, коли об’єкт моделі потребує методів, що працюють над його власними властивостями, які я, ймовірно, буду використовувати скрізь (наприклад, вдосконалені геттер / сеттер). Я не закликаю робити це для кожної служби систематично.

EDIT: Раніше я думав, що цей шаблон буде протиставлятися мантрі "Кутова модель - це звичайний старий об'єкт JavaScript", але мені зараз здається, що ця картина ідеально чудова.

EDIT (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
}

модель / Element.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;
});

4
Я просто впадаю в Кутовий, але мені буде цікаво дізнатися, чи / чому ветерани вважають, що це єресь. Це, мабуть, так, як я спочатку підходив би до цього. Чи може хтось надати відгук?
Аароній

2
@Aaronius просто для того, щоб бути зрозумілим: я ніколи не читав "ти ніколи цього не робиш" на будь-якому документі або в блогах angularjs, але я завжди читав такі речі, як "angularjs не потрібна модель, просто використовується звичайний старий javascript" , і мені довелося відкрити цю закономірність самостійно. Оскільки це мій перший реальний проект на AngularJS, я висуваю ці чіткі застереження, щоб люди не копіювали / не вставляли, не думаючи спочатку.
Ben G

Я влаштувався приблизно за подібною схемою. Прикро, що Angular не має реальної підтримки (або, здавалося б, бажання підтримати) модель в "класичному" розумінні.
drt

3
Для мене це не виглядає єресі, ви використовуєте фабрики для того, для чого вони були створені: будівництво об’єктів. Я вважаю, що фразі "angularjs не потрібна модель" означає "вам не потрібно успадковувати з спеціального класу або використовувати спеціальні методи (наприклад, ko.observable, в нокауті) для роботи з моделями в кутовій, а чистого js об’єкта буде достатньо ".
Феліпе Кастро

1
Чи не матиме відповідний ім’я ElementService для кожної колекції, що призведе до набору майже однакових файлів?
Collin Allen

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 є парадигмою, і як такої немає ніякого кутового способу JS, як мовна підтримка DCI, так і ні. JS досить добре підтримує DCI, якщо ви готові скористатися перетворенням джерела, а також з деякими недоліками, якщо ви цього не зробите. Знову ж таки, DCI не має більше нічого спільного з ін'єкцією залежностей, ніж, скажімо, клас C # і, безумовно, теж не є послугою. Таким чином, найкращий спосіб зробити DCI з angulusJS - це зробити DCI JS способом, що досить близько до того, як формулюється DCI в першу чергу. Якщо ви не зробите трансформацію джерела, ви не зможете це зробити повністю, оскільки рольові методи будуть частиною об'єкта навіть поза контекстом, але це, як правило, проблема з методом DCI, заснованого на ін'єкції. Якщо подивитися на fullOO.infoна авторитетному сайті для DCI ви можете ознайомитись з рубіновими реалізаціями, вони також використовують метод введення або ви можете ознайомитись тут, щоб отримати додаткову інформацію про 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. Це чудове читання. Але мої запитання дійсно спрямовані на те, "куди розмістити модельні об'єкти в кутових". DCI якраз там для ознайомлення, що я, можливо, не тільки маю модель, але і розділяю її DCI способом. Відредагуйте питання, щоб зробити його більш зрозумілим.
Nils Blum-Oeste

7

Ця стаття про моделі в AngularJS може допомогти:

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


7
Зауважте, що відповіді, що стосуються лише посилань, не відштовхують, відповіді на відповідність повинні бути кінцевою точкою пошуку рішення (проти ще однієї зупинки посилань, яка, як правило, затягується з часом). Будь ласка, розглянути можливість додавання сюди окремого конспекту, зберігаючи посилання як орієнтир.
Клеопатра

додавши таке посилання у коментар до питання, було б добре.
jorrebor

Це посилання насправді є дуже хорошою статтею, але це буде потреба бути складеним відповіддю, щоб відповідати SO
Jeremy Zerr

5

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

  1. Методи взаємодії з API RESTful та створення нових об'єктів
  2. Встановлення зв’язків між моделями
  3. Перевірка даних перед збереженням резервного копіювання; також корисно для відображення помилок у режимі реального часу
  4. Кешування та ледача завантаження, щоб не робити марних HTTP-запитів
  5. Гачки державних машин (до / після збереження, оновлення, створення, нового тощо)

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

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


Гей! Це справді чудово! Я зараз підключу його до свого додатка. Бойові випробування тільки почалися.
Дж. Бруні

1
Я як би просто дивився на вашу посаду і цікавився, чим відрізняються послуги вашої ngActiveResourceта Angular $resource. Я трохи новачок у Angular і швидко переглянув обидва набори документів, але вони, здається, пропонують багато перекриттів. Чи був ngActiveResourceрозроблений до надання $resourceпослуги?
Ерік Б.

5

Питання старіше, але я вважаю, що тема є більш актуальною, ніж будь-коли, з огляду на новий напрямок Angular 2.0. Я б сказав, що найкращою практикою є написання коду з якомога меншою кількістю залежностей від певної структури. Використовуйте лише окремі рамки, де це додає прямого значення.

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

Ось приклад малюнка, який я вважаю гарним для роз'єднання об'єктної моделі від dom.

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

Основна мета - структурувати свій код таким чином, щоб зробити його таким же простим у використанні з одиничних тестів, як і з перегляду. Якщо ви досягнете цього, ви зможете написати реалістичні та корисні тести.


4

Я намагався вирішити цю проблему в цій публікації блогу .

В основному, найкращий будинок для моделювання даних - це служби та фабрики. Однак залежно від того, як ви отримуєте свої дані та складності поведінки, яка вам потрібна, існує багато різних способів здійснити реалізацію. На сьогодні кутовий не має стандартного способу чи найкращої практики.

Пост охоплює три підходи, використовуючи $ 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 $ ресурс VS Restangular

Є можливість Angular 2.0 запропонувати більш надійне рішення для моделювання даних, яке отримує всіх на одній сторінці.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.