Благодаря огромному количеству ценных источников я получил несколько общих рекомендаций по реализации компонентов в приложениях AngularJS:
контроллер
Контроллер должен быть просто прослойкой между моделью и видом. Постарайтесь сделать его максимально тонким .
Настоятельно рекомендуется избегать бизнес-логики в контроллере. Следует перенести в модель.
Контроллер может связываться с другими контроллерами, используя вызов метода (возможно, когда дети хотят общаться с родителем) или методы $ emit , $ broadcast и $ on . Передаваемые и передаваемые сообщения должны быть сведены к минимуму.
Контроллер не должен заботиться о представлении или манипулировании DOM.
Старайтесь избегать вложенных контроллеров . В этом случае родительский контроллер интерпретируется как модель. Вместо этого введите модели как общие службы.
Область применения контроллера должен использоваться для связывания модели с учетом и
герметизирующего View Model , как для представления модели шаблона проектирования.
Объем
Обрабатывать область видимости только для чтения в шаблонах и только для записи в контроллерах . Цель этой области - обратиться к модели, а не к модели.
При выполнении двунаправленной привязки (ng-model) убедитесь, что вы не привязываете напрямую к свойствам области.
Модель
Модель в AngularJS является одноэлементной, определяемой сервисом .
Модель предоставляет отличный способ разделения данных и отображения.
Модели являются основными кандидатами для модульного тестирования, поскольку они обычно имеют ровно одну зависимость (некоторую форму генератора событий, в общем случае $ rootScope ) и содержат хорошо проверяемую логику домена .
Модель следует рассматривать как реализацию конкретной единицы. Он основан на принципе единой ответственности. Модуль - это экземпляр, который отвечает за собственную область связанной логики, которая может представлять отдельную сущность в реальном мире и описывать ее в мире программирования с точки зрения данных и состояния .
Модель должна инкапсулировать данные вашего приложения и предоставлять API
для доступа к этим данным и манипулирования ими.
Модель должна быть портативной, чтобы ее можно было легко транспортировать в аналогичное приложение.
Выделив логику модуля в своей модели, вы упростили поиск, обновление и обслуживание.
Модель может использовать методы более общих глобальных моделей, которые являются общими для всего приложения.
Старайтесь избегать встраивания других моделей в вашу модель, используя внедрение зависимостей, если это на самом деле не зависит от уменьшения связи компонентов и повышения тестируемости и удобства использования .
Старайтесь избегать использования слушателей событий в моделях. Это затрудняет их тестирование и в целом убивает модели с точки зрения принципа единой ответственности.
Реализация модели
Поскольку модель должна инкапсулировать некоторую логику с точки зрения данных и состояния, она должна архитектурно ограничивать доступ к своим членам, поэтому мы можем гарантировать слабую связь.
Способ сделать это в приложении AngularJS - определить его, используя тип сервиса фабрики . Это позволит нам очень легко определять частные свойства и методы, а также возвращать общедоступные в одном месте, что сделает его действительно читабельным для разработчика.
Пример :
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
};
});
Создание новых экземпляров
Старайтесь избегать фабрики, которая возвращает новую функциональную функцию, поскольку это начинает ломать внедрение зависимостей, и библиотека будет вести себя неловко, особенно для третьих лиц.
Лучший способ сделать то же самое - использовать фабрику как 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.
Ресурсы внедряются в модели и предоставляют возможность отправлять / извлекать данные.
Реализация ресурсов
Фабрика, которая создает объект ресурса, который позволяет вам взаимодействовать с RESTful-источниками данных на стороне сервера.
Возвращенный объект ресурса имеет методы действия, которые обеспечивают высокоуровневое поведение без необходимости взаимодействия со службой $ http низкого уровня.
Сервисы
И модель, и ресурс являются услугами .
Сервисы - это несвязанные, слабо связанные блоки функциональности, которые являются автономными.
Сервисы - это функция, которую Angular предоставляет клиентским веб-приложениям со стороны сервера, где сервисы обычно используются в течение длительного времени.
Службы в приложениях Angular являются заменяемыми объектами, которые связаны друг с другом с помощью внедрения зависимостей.
Angular поставляется с различными видами услуг. Каждый со своими вариантами использования. Пожалуйста, прочитайте Понимание типов услуг для деталей.
Попробуйте рассмотреть основные принципы архитектуры сервиса в вашем приложении.
В целом, согласно Глоссарию веб-сервисов :
Сервис - это абстрактный ресурс, который представляет возможность выполнения задач, которые формируют согласованную функциональность с точки зрения сущностей провайдеров и сущностей запрашивающих сторон. Для использования услуга должна быть реализована конкретным агентом поставщика.
Клиентская структура
В целом клиентская часть приложения разбита на модули . Каждый модуль должен быть тестируемым как единое целое.
Попробуйте определить модули в зависимости от функции / функциональности или вида , а не по типу. Смотрите презентацию Миско для деталей.
Компоненты модуля могут быть условно сгруппированы по типам, таким как контроллеры, модели, представления, фильтры, директивы и т. Д.
Но сам модуль остается многоразовым , передаваемым и тестируемым .
Разработчикам также намного проще найти некоторые части кода и все его зависимости.
Пожалуйста, обратитесь к организации кода в больших 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-app - https://github.com/angular-app/angular-app/tree/master/client/src
Это также учитывается современными генераторами приложений - https://github.com/yeoman/generator-angular/issues/109