AngularJS: понимание структуры дизайна

В контексте этот пост Игоря Минара, ведущего AngularJS:

MVC vs MVVM vs MVP. Какая спорная тема, которую многие разработчики могут часами и часами обсуждать и спорить.

В течение нескольких лет AngularJS был ближе к MVC (или, скорее, одному из своих клиентские варианты), но со временем и благодаря многим рефакторингам и api, теперь он ближе к MVVM - объекту $scopeможно считать ViewModel, который украшают функцию, которую мы называем Контроллер.

Возможность классифицировать фреймворк и помещать его в один из ведра MV * имеет некоторые преимущества. Это может помочь разработчикам более комфортно с помощью apis, сделав это легче создать ментальную модель, которая представляет приложение, которое строится с каркасом. Это также может помочь установить терминология, используемая разработчиками.

Сказав, я бы предпочел, чтобы разработчики разрабатывали приложения для ударных приложений, которые хорошо спроектированы и следуют разделению проблем, чем видеть их отходы время рассуждая о бессмысленности М.В. И поэтому я настоящим заявляю AngularJS будет MVW framework - Model-View-Whatever. Где бы ни было означает ", что сработает для вас".

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

Существуют ли какие-либо рекомендации или рекомендации по внедрению шаблона проектирования AngularJS MVW (Model-View-Whatever) в клиентских приложениях?

Ответ 1

Благодаря огромному количеству ценных источников у меня есть некоторые общие рекомендации по внедрению компонентов в приложениях AngularJS:


Controller

  • Контроллер должен быть просто промежуточным слоем между моделью и представлением. Постарайтесь сделать его максимально тонким.

  • Настоятельно рекомендуется избегать бизнес-логики в контроллере. Его следует перенести в модель.

  • Контроллер может связываться с другими контроллерами, используя вызов метода (возможно, когда дети хотят общаться с родителем) или $emit, $broadcast и $on. Испускаемые и транслируемые сообщения должны быть сведены к минимуму.

  • Контроллер должен не заботиться о презентации или DOM-манипуляции.

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

  • Область в контроллере должна использоваться для привязки модели с представлением и
    инкапсуляция Показать модель как для шаблона Модель представления.


Сфера

Отредактировать область как только для чтения в шаблонах и только для записи в контроллерах. Целью области является ссылка на модель, а не модель.

При двунаправленном привязке (ng-model) убедитесь, что вы не привязываетесь непосредственно к свойствам области.


Model

Модель в AngularJS является singleton, определяемой службой.

Модель предоставляет отличный способ разделения данных и отображения.

Модели являются первыми кандидатами для модульного тестирования, так как они обычно имеют ровно одну зависимость (некоторая форма излучателя события, в общем случае $rootScope) и содержат высоко проверяемую логику домена.

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

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

  • Модель должна быть переносимой, чтобы ее можно было легко переносить на аналогичные приложение.

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

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

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

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

Реализация модели

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

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

Пример:

angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {

  var itemsPerPage = 10,
  currentPage = 1,
  totalPages = 0,
  allLoaded = false,
  searchQuery;

  function init(params) {
    itemsPerPage = params.itemsPerPage || itemsPerPage;
    searchQuery = params.substring || searchQuery;
  }

  function findItems(page, queryParams) {
    searchQuery = queryParams.substring || searchQuery;

    return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
      totalPages = results.totalPages;
      currentPage = results.currentPage;
      allLoaded = totalPages <= currentPage;

      return results.list
    });
  }

  function findNext() {
    return findItems(currentPage + 1);
  }

  function isAllLoaded() {
    return allLoaded;
  }

  // return public model API  
  return {
    /**
     * @param {Object} params
     */
    init: init,

    /**
     * @param {Number} page
     * @param {Object} queryParams
     * @return {Object} promise
     */
    find: findItems,

    /**
     * @return {Boolean}
     */
    allLoaded: isAllLoaded,

    /**
     * @return {Object} promise
     */
    findNext: findNext
  };
});

Создание новых экземпляров

Старайтесь не иметь factory, который возвращает новую работоспособную функцию, так как это начинает разрушать инъекцию зависимостей, и библиотека будет вести себя неловко, особенно для третьих лиц.

Лучший способ сделать то же самое - использовать factory как API для возврата коллекции объектов с методами getter и setter, прикрепленными к ним.

angular.module('car')
 .factory( 'carModel', ['carResource', function (carResource) {

  function Car(data) {
    angular.extend(this, data);
  }

  Car.prototype = {
    save: function () {
      // TODO: strip irrelevant fields
      var carData = //...
      return carResource.save(carData);
    }
  };

  function getCarById ( id ) {
    return carResource.getById(id).then(function (data) {
      return new Car(data);
    });
  }

  // the public API
  return {
    // ...
    findById: getCarById
    // ...
  };
});

Глобальная модель

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

В частном случае некоторые методы требуют глобальной доступности в приложении. Чтобы было возможно, вы можете определить "общее свойство в $rootScope и привязать его к commonModel во время загрузки приложения:

angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
  $rootScope.common = 'commonModel';
}]);

Все ваши глобальные методы будут жить в рамках общей собственности. Это своего рода пространство имен.

Но не определяйте какие-либо методы непосредственно в вашем $rootScope. Это может привести к непредвиденному поведению при использовании с директивой ngModel в пределах вашей области видимости, как правило, засоряя вашу область действия и приводит к тому, что методы области превышают основные проблемы.


Ресурс

Ресурс

позволяет вам взаимодействовать с различными источниками данных.

Должно быть реализовано с использованием принципа единой ответственности.

В частном случае это прокси-сервер для повторного использования для конечных точек HTTP/JSON.

Ресурсы вводятся в модели и предоставляют возможность отправлять/извлекать данные.

Реализация ресурсов

A factory, который создает объект ресурса, который позволяет вам взаимодействовать с источниками данных сервера на сервере RESTful.

Возвращенный объект ресурса имеет методы действий, которые обеспечивают поведение высокого уровня без необходимости взаимодействия с низкоуровневой службой $http.


Услуги

И модель, и ресурс - это службы.

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

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

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

Angular поставляется с различными типами сервисов. Каждый из них имеет свои собственные варианты использования. Подробнее см. Общие сведения о типах услуг.

Попробуйте рассмотреть основные принципы архитектуры обслуживания в вашем приложении.

В целом согласно Глоссарий веб-сервисов:

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


Структура на стороне клиента

В целом клиентская сторона приложения разделена на модули. Каждый модуль должен быть testable как единица.

Попробуйте определить модули в зависимости от функции/функциональности или просмотра, а не по типу. Подробнее см. презентация Miskos.

Компоненты модуля можно условно сгруппировать по типам, таким как контроллеры, модели, представления, фильтры, директивы и т.д.

Но сам модуль остается повторно используемым, переносимым и проверяемым.

Разработчикам гораздо проще найти некоторые части кода и все его зависимости.

Подробнее см. Организация кода в приложениях Large AngularJS и JavaScript.

Пример структурирования папок:

|-- src/
|   |-- app/
|   |   |-- app.js
|   |   |-- home/
|   |   |   |-- home.js
|   |   |   |-- homeCtrl.js
|   |   |   |-- home.spec.js
|   |   |   |-- home.tpl.html
|   |   |   |-- home.less
|   |   |-- user/
|   |   |   |-- user.js
|   |   |   |-- userCtrl.js
|   |   |   |-- userModel.js
|   |   |   |-- userResource.js
|   |   |   |-- user.spec.js
|   |   |   |-- user.tpl.html
|   |   |   |-- user.less
|   |   |   |-- create/
|   |   |   |   |-- create.js
|   |   |   |   |-- createCtrl.js
|   |   |   |   |-- create.tpl.html
|   |-- common/
|   |   |-- authentication/
|   |   |   |-- authentication.js
|   |   |   |-- authenticationModel.js
|   |   |   |-- authenticationService.js
|   |-- assets/
|   |   |-- images/
|   |   |   |-- logo.png
|   |   |   |-- user/
|   |   |   |   |-- user-icon.png
|   |   |   |   |-- user-default-avatar.png
|   |-- index.html

Хороший пример структурирования приложений Angular реализуется с помощью angular -app - https://github.com/angular-app/angular-app/tree/master/client/src

Это также рассматривается современными генераторами приложений - https://github.com/yeoman/generator-angular/issues/109

Ответ 2

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

MVC и его производные (MVP, PM, MVVM) являются хорошими и денди в рамках одного агента, но архитектура сервер-клиент для всех целей является системой с двумя агентами, а люди часто настолько одержимы этими шаблонами, что они забывают, что проблема под рукой намного сложнее. Пытаясь придерживаться этих принципов, на самом деле они заканчиваются недостатком архитектуры.

Сделайте это по частям.

Рекомендации

представления

В контексте Angular представление представляет собой DOM. Руководящие принципы:

делать:

  • Текущая переменная области (только для чтения).
  • Вызовите контроллер для действий.

Не

  • Поставьте любую логику.

Как заманчиво, коротко и безвредно это выглядит:

ng-click="collapsed = !collapsed"

В значительной степени это означает, что любой разработчик теперь понимает, как работает система для проверки как файлов Javascript, так и HTML файлов.

Контроллеры

делать:

  • Привязать представление к "модели" путем размещения данных в области.
  • Отвечайте на действия пользователя.
  • Сделка с логикой представления.

Не

  • Сделка с любой бизнес-логикой.

Причиной последнего руководства является то, что контроллеры являются сёстрами, а не сущностями; и они не могут использоваться повторно.

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

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

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

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

Итак, если контроллеры не должны иметь дело с бизнес-логикой, кто должен?

Что такое модель?

Ваша клиентская модель часто является частичной и устаревшей

Если вы не пишете автономное веб-приложение или приложение, которое ужасно просто (несколько сущностей), ваша модель клиента, скорее всего, будет:

  • Частичная
    • Либо он не имеет всех объектов (например, в случае разбивки на страницы)
    • Или у него нет всех данных (например, в случае разбивки на страницы)
  • Stale. Если система имеет более одного пользователя, в любой момент вы не можете быть уверены, что модель, которую поддерживает клиент, совпадает с той, что хранится в сервере.

Реальная модель должна сохраняться

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

Последствия

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

Таким образом, в контексте клиента, возможно, разумно использовать нижний регистр M, поэтому он действительно mVC, mVP и mVVm. Большой M для сервера.

Бизнес-логика

Возможно, одна из наиболее важных концепций бизнес-моделей состоит в том, что вы можете разделить их на 2 типа (я опускаю третий вид бизнес-бизнеса как историю на другой день):

  • Логика домена - aka Бизнес-правила предприятия, логика, не зависящая от приложения. Например, дайте модель с параметрами firstName и sirName, такой же, как и для getFullName(), может быть не зависимым от приложения.
  • Логика приложений - aka Бизнес-правила приложений, которые являются специфичными для приложения. Например, проверка и обработка ошибок.

Важно подчеркнуть, что оба из них в контексте клиента не "реальная" бизнес-логика - они касаются только той части, которая важна для клиента. Логика приложения (а не логика домена) должна нести ответственность за облегчение связи с сервером и большинство взаимодействия с пользователем; в то время как логика домена в значительной степени является мелкомасштабной, ориентированной на сущность и управляемой презентацией.

Вопрос по-прежнему остается - где вы их бросаете в приложение Angular?

3 против архитектуры с 4 уровнями

Все эти среды MVW используют 3 слоя:

Three circles. Inner - model, middle - controller, outer - view

Но есть две основные проблемы с этим, когда дело касается клиентов:

  • Модель является частичной, устаревшей и не сохраняется.
  • Нет места для размещения логики приложения.

Альтернативой этой стратегии является 4-уровневая стратегия:

4 circles, from inner to outer - Enterprise business rules, Application business rules, Interface adapters, Frameworks and drivers

Реальная сделка здесь - это уровень бизнес-правил приложений (Use cases), который часто не работает на клиентах.

Этот уровень реализуется интеракторами (дядя Боб), что в значительной степени напоминает Мартин Фаулер на рабочем уровне script.

Конкретный пример

Рассмотрим следующее веб-приложение:

  • Приложение отображает список пользователей с разбивкой по страницам.
  • Пользователь нажимает "Добавить пользователя".
  • Модель открывается с формой, чтобы заполнить детали пользователя.
  • Пользователь заполняет форму и нажимает submit.

Теперь должно произойти несколько вещей:

  • Форма должна быть проверена клиентом.
  • Запрос должен быть отправлен на сервер.
  • Ошибка должна быть обработана, если она есть.
  • Список пользователей может или не может (из-за разбиения на страницы) нуждаться в обновлении.

Где мы все это делаем?

Если ваша архитектура включает контроллер, который вызывает $resource, все это произойдет в контроллере. Но есть лучшая стратегия.

Предлагаемое решение

На следующей диаграмме показано, как проблему можно решить, добавив еще один логический уровень приложения в Angular клиентах:

4 boxes - DOM points to Controller, which points to Application logic, which points to $resource

Итак, мы добавляем слой между контроллером в $resource, этот слой (позволяет называть его интерактором):

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

Итак, с требованиями конкретного примера выше:

  • Пользователь нажимает "Добавить пользователя".
  • Контроллер запрашивает интерактор для пустой пользовательской модели, украшен бизнес-логическим методом, например validate()
  • При представлении контроллер вызывает метод модели validate().
  • Если это не удается, контроллер обрабатывает ошибку.
  • В случае успеха контроллер вызывает интерактор с createUser()
  • Взаимодействие вызывает $resource
  • После ответа интерактор делегирует любые ошибки контроллеру, который обрабатывает их.
  • После успешного ответа, интерактор гарантирует, что при необходимости обновится список пользователей.

Ответ 3

Небольшая проблема по сравнению с замечательными советами в Артемском ответе, но с точки зрения удобочитаемости кода я нашел лучшее определение API полностью внутри объекта return, чтобы свести к минимуму возврат взад и вперед в коде, чтобы посмотреть, что переменные wheverer определено:

angular.module('myModule', [])
// or .constant instead of .value
.value('myConfig', {
  var1: value1,
  var2: value2
  ...
})
.factory('myFactory', function(myConfig) {
  ...preliminary work with myConfig...
  return {
    // comments
    myAPIproperty1: ...,
    ...
    myAPImethod1: function(arg1, ...) {
    ...
    }
  }
});

Если объект return становится "слишком переполненным", это признак того, что Сервис делает слишком много.

Ответ 4

AngularJS не реализует MVC традиционным способом, а скорее реализует что-то ближе к MVVM (Model-View-ViewModel), ViewModel также может упоминаться как связующее (в случае angular это может быть $scope). Модель → Как нам известно, модель в angular может быть просто старыми JS-объектами или данными в нашем приложении

Вид → вид в angularJS - это HTML, который был разобран и скомпилирован angularJS, применяя директивы или инструкции или привязки. Основная точка здесь находится в angular, входной - это не просто простая строка HTML ( innerHTML), скорее это DOM, созданный браузером.

ViewModel → ViewModel на самом деле является связующим/мостом между вашим представлением и моделью в angularJS случае это $scope, чтобы инициализировать и увеличить область $, которую мы используем Controller.

Если я хочу обобщить ответ: В приложении angularJS $scope имеет ссылку на данные, Controller контролирует поведение, а View обрабатывает макет, взаимодействуя с контроллером, чтобы вести себя соответственно.

Ответ 5

Чтобы быть четким в вопросе, Angular использует разные шаблоны проектирования, которые мы уже встречали в нашем обычном программировании. 1) Когда мы регистрируем наши контроллеры или директивы, factory, сервисы и т.д. В отношении нашего модуля. Здесь он скрывает данные из глобального пространства. Какая модель модуля. 2) Когда Angular использует свою грязную проверку для сравнения переменных области, здесь используется Шаблон наблюдения. 3) Все области родительского дочернего элемента в наших контроллерах используют шаблон Prototypal. 4) В случае инъекции услуг он использует Factory Шаблон.

В целом он использует различные известные шаблоны проектирования для решения проблем.