#Введение
Цель данного гида — показать набор лучших практик для AngularJS приложений.
Которые были собранны из следующих источников:
- Исходный код AngularJS.
- Мной прочитанные статьи.
- Мой собственный опыт.
Замечание: это все еще черновик, главная цель которого — это-то, чтобы его развивало сообщество и поэтому восполнение любых пробелов будет принято с благодарностью.
В этом руководстве вы не найдете общих требований к стилю для разработки на JavaScript. Они есть тут:
- Google's JavaScript style guide
- Mozilla's JavaScript style guide
- GitHub's JavaScript style guide
- Douglas Crockford's JavaScript style guide
Для AngularJS разработчиков рекомендуется придерживаться этого гида: Google's JavaScript style guide.
На wiki странице GitHub репозитория AngularJS есть похожая секция созданная ProLoser, вы можете посмотреть ее здесь.
#Содержание
#Общие
Так как большое AngularJS приложение состоит из множества компонентов оптимальный способ их структурирования — иерархия каталогов.
Есть два основных подхода:
- В начале разделить по типам компонент, а затем по функциональности:
В таком случае структура каталогов будет выглядеть так:
.
├── app
│ ├── app.js
│ ├── controllers
│ │ ├── page1
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ └── page2
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── page1
│ │ │ └── directive1.js
│ │ └── page2
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ ├── page1
│ │ └── page2
│ └── services
│ ├── CommonService.js
│ ├── cache
│ │ ├── Cache1.js
│ │ └── Cache2.js
│ └── models
│ ├── Model1.js
│ └── Model2.js
├── lib
└── test
- В начале разделить по функциональности, а затем по типам компонентов:
Вот как это выглядит:
.
├── app
│ ├── app.js
│ ├── common
│ │ ├── controllers
│ │ ├── directives
│ │ ├── filters
│ │ └── services
│ ├── page1
│ │ ├── controllers
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ ├── directives
│ │ │ └── directive1.js
│ │ ├── filters
│ │ │ ├── filter1.js
│ │ │ └── filter2.js
│ │ └── services
│ │ ├── service1.js
│ │ └── service2.js
│ └── page2
│ ├── controllers
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ └── filter3.js
│ └── services
│ └── service3.js
├── lib
└── test
-
Когда создается директива, может быть очень удобным положить все связанные с ней файлы в одну директорию (например шаблон, CSS/SASS, JavaScript). Если вы выберите такой подход будьте последовательны и используйте его везде в своем проекте.
app └── directives ├── directive1 │ ├── directive1.html │ ├── directive1.js │ └── directive1.sass └── directive2 ├── directive2.html ├── directive2.js └── directive2.sass
Этот подход может сочетаться с любым из двух предложенных выше.
-
Еще один способ немного отличающийся от остальных, он используется например в ng-boilerplate. И заключается в том, что unit тесты находятся внутри папки компонента. Это помогает вам, когда вы вносите какие-то изменения в компонент быстро найти его тесты, также при таком подходе, тесты играют роль документации и показывают примеры использования.
services ├── cache │ ├── cache1.js │ └── cache1.spec.js └── models ├── model1.js └── model1.spec.js
-
Файл
app.js
содержит определения маршрутов, конфигурацию и/или начальную инициализацию (если требуется). -
Каждый JavaScript файл должен содержать только один компонент. Имя файла должно соответствовать названию компонента.
-
Используйте шаблоны структур AngularJS проектов например такие как Yeoman, ng-boilerplate.
Я предпочитаю первый, потому что в такой структуре проще найти общие компоненты.
Соглашения об именовании компонентов могут быть найдены в соответствующих секциях.
- Следите только за жизненно важными переменным (для примера: когда вы используете real-time коммуникации не вызывайте цикл обработки в каждом полученном сообщении).
- Для контента, который меняется только раз, используйте одноразовые watch, например,
bindonce
. - Сделайте вычисления в
$watch
максимально простыми. Создание сложных вычислений в$watch
замедляет выполнение всего приложения. (цикл$digest
работает в одном потоке, потому что JavaScript однопоточный).
- Используйте:
$timeout
вместоsetTimeout
$window
вместоwindow
$document
вместоdocument
$http
вместо$.ajax
Это сделает ваше тестирование гораздо проще и в некоторых случае убережет от неожиданного поведения (например, если вы забудете $scope.$apply
в setTimeout
).
-
Автоматизируйте ваши процессы с помощью следующих инструментов:
-
Используйте промисы (
$q
) взамен callback`ов. Что сделает ваш код более элегантным и чистым и спасет вас от callback hell. -
Используйте
$resource
вместо$http
где это возможно. Более высокий уровень абстракций убережет вас от избыточного кода. -
Используйте AngularJS pre-minifier (такой, как ngmin или ng-annotate) для избежания проблем после сжатия скриптов.
-
Не используйте глобальное пространство имен. Разрешайте все зависимости с помощью Dependency Injection.
-
Не захламляйте ваш
$scope
. Добавляйте только те переменные и функции, который будут использованы в шаблонах. -
Используйте контроллеры вместо
ngInit
.
#Модули
Существует два общих способа для структурирования модулей:
- По функциональности.
- По типу компонента.
На самом деле они не очень то и отличаются, но первый путь выглядит чище. Также если ленивая загрузка модулей будет когда-нибудь реализована (в настоящее время нет в планах AngularJS) — это бы улучшило производительность приложения.
#Контроллеры
-
Не работайте с DOM из контроллеров. Используйте для этого директивы.
-
Именовать контроллеры правильно так, чтобы его имя остояло из части описывающей то чем он занимается (для примера: корзина, домашняя страница, админ панель) и постфикса
Ctrl
. Имена контроллеров записываются в UpperCamelCase (HomePageCtrl
,ShoppingCartCtrl
,AdminPanelCtrl
, и т.д.). -
Контроллеры не должны быть объявлены в глобальном пространстве (даже несмотря на то, что AngularJS позволяет сделать это, это плохая практика, которая ведет к его загрязнению).
-
Используйте синтаксис массивов для объявления контроллеров:
module.controller('MyCtrl', ['dependency1', 'dependency2', ..., 'dependencyn', function (dependency1, dependency2, ..., dependencyn) { //...body }]);
Использование такого типа определения избавляет от проблем с минификицией файлов. Вы можете автоматически сгенерировать определение с синтаксисом массива используя инструмент такой, как ng-annotate (и задачи для grunt grunt-ng-annotate).
-
Используйте оригинальные имена для зависимостей контроллера. Что поможет вам в создании более понятного кода:
module.controller('MyCtrl', ['$scope', function (s) { //...body }]);
Хуже читается чем:
module.controller('MyCtrl', ['$scope', function ($scope) {
//...body
}]);
Особенно для больших файлов со множеством строк кода, который придется проскролить весь, чтобы понять, что есть что. Что в итоге может вызвать затруднения связанные с тем, что вы забыли какая переменная отвечает за какую зависимость.
-
Держите контроллеры настолько маленькими на сколько это возможно. Вынесите общие функции в сервисы.
-
Организовывайте коммуникацию между контроллерами используя вызовы методов (например когда дети хотят связаться с родителями) или
$emit
,$broadcast
и$on
методы. Количество$emit
,$broadcast
сообщений должно быть сведено к минимуму. -
Сделайте список со всеми сообщениями пересылаемыми с помощью
$emit
,$broadcast
и тщательно следите за ними из-за возможных коллизий имен и других багов. -
Когда вам нужно отформатировать данные перенесите логику форматирования в фильтр и укажите его как зависимость:
module.filter('myFormat', function () { return function () { //body... }; }); module.controller('MyCtrl', ['$scope', 'myFormatFilter', function ($scope, myFormatFilter) { //body... }]);
#Директивы
- Называйте ваши директивы используя lowerCamelCase.
- Используйте
scope
вместо$scope
в вашей link функции. В compile, post/pre функциях у вас уже будет предустановленный набор аргументов, которые будут переданы когда функция вызывается, у вас не будет возможности изменить их использую DI. Такой стиль используется и внутри самого AngularJS. - Используйте кастомные префиксы для ваших директив во избежании коллизий со сторонними библиотеками.
- Не используйте префиксы
ng
илиui
, так как они зарезервированы для использования в AngularJS и AngularJS UI. - Манипуляции с DOM должны производиться только с помощью директив.
- Создавайте изолированный scope когда вы разрабатываете переиспользуемые компоненты.
#Фильтры
- Называйте ваши фильтры используя lowerCamelCase.
- Сохраняйте ваши фильтры настолько простыми насколько это возможно. Они часто вызываются во время
$digest
цикла, так что медленный фильтр тормозит все приложение.
#Сервисы
- Используйте camelCase для имен сервисов.
- Инкапсулируйте бизнес-логику в сервисы.
- Инкапусляция бизнес-логики в сервис предпочтительна с использованием
service
вместоfactory
. - Для кеширования на уровне сессии вы можете использовать
$cacheFactory
. Она должна использоваться для кеширования результатов запросов или тяжелых вычислений.
#Шаблоны
-
Используйте
ng-bind
илиng-cloak
вместо простого{{ }}
для предотвращения мигания контента. -
Избегайте написания сложного кода в шаблонах.
-
Когда вам нужно установить аттрибут
src
у картинки динамически, используйтеng-src
вместоsrc
внутри содержащего шаблон{{ }}
. -
Вместо использования строковой переменной в scope и использовании ее в атрибуте
style
через шаблон{{ }}
, используйте директивуng-style
с объектом в scope переменной, как значение:... $scope.divStyle = { width: 200, position: 'relative' }; ... <div ng-style="divStyle">my beautifully styled div which will work in IE</div>;
#Маршрутизация
- Используйте
resolve
для разрешения зависимостей перед тем, как представление будет показано.