Pull to refresh

Смелый стайлгайд по AngularJS для командной разработки [1/2]

Reading time 5 min
Views 39K
Original author: Todd Motto
После прочтения Google's AngularJS Guidelines у меня создалось впечатление о его незавершённости, а ещё в нём часто намекали на профит от использования библиотеки Closure. Ещё они заявили, «Мы не думаем, что эти рекомендации одинаково хорошо применимы для всех проектов, использующих AngularJS. Мы будем рады видеть инициативу от сообщества за более общий стайлгайд, применимый как для небольших так и крупных проектов».

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

Определение модулей


Модули в AngularJS могут быть объявлены различными способами: либо с использованием переменной, либо через getter-синтаксис. Всегда используйте второй способ (более того, он рекомендован разработчиками фреймворка).

Плохо:

var app = angular.module('app', []);
app.controller();
app.factory();

Хорошо:

angular
  .module('app', [])
  .controller()
  .factory();

Функции и методы модуля


В модулях Angular есть много различных методов, таких как controller, factory, directive, service и др. Есть также много различных синтаксисов для модулей, когда речь заходит о DI и форматировании кода. Используйте определение именованных функции, чтобы потом передавать их названия соответствующим методам. Такой способ даёт больше возможности при трассировке стека, так как функции не являются анонимными (конечно, можно просто начать использовать именованные функции вместо анонимных, но такой способ более удобочитаем).

Плохо:

var app = angular.module('app', []);
app.controller('MyCtrl', function () {

});

Хорошо:

function MainCtrl () {

}

angular
  .module('app', [])
  .controller('MainCtrl', MainCtrl);

Определяйте модуль однажды используя setter-синтаксис так: angular.module('app', []). Затем, если понадобиться обратиться к этому модулю (например, в других файлах), используйте getter-синтаксис: angular.module('app').

А для того, чтобы не загрязнять глобальную область видимости, просто оберните весь свой код в IIFE.

Отлично:

(function () {
  angular.module('app', []);
  
  // MainCtrl.js
  function MainCtrl () {

  }
  
  angular
    .module('app')
    .controller('MainCtrl', MainCtrl);
    
  // AnotherCtrl.js
  function AnotherCtrl () {
  
  }
  
  angular
    .module('app')
    .controller('AnotherCtrl', AnotherCtrl);
    
  // и так далее...
    
})();

Контроллеры


В виду того, что контроллеры – это классы, помимо привычного controller-синтаксиса у них есть controllerAs-синтаксис. Используйте именно его, так как помимо возможности ссылаться на экземпляр контроллера, такой способ делает скоупы вложенными.

Привязка к DOM через controllerAs


Плохо:

<div ng-controller="MainCtrl">
  {{ someObject }}
</div>

Хорошо:

<div ng-controller="MainCtrl as main">
  {{ main.someObject }}
</div>

Стоит также заметить, что способ привязки к DOM через аттрибут ng-controller во многом ограничивает применение данного представления (view) только в паре с указанным контроллером. И хотя редко, но всё же бывают ситуации, когда одно и то же представление может быть использовано с разными контроллерами. Для большей гибкости в данном вопросе, используйте роутер для связи view с контроллером.

Отлично:

<!-- main.html -->
<div>
  {{ main.someObject }}
</div>
<!-- main.html -->

<script>
// ...
function config ($routeProvider) {
  $routeProvider
  .when('/', {
    templateUrl: 'views/main.html',
    controller: 'MainCtrl',
    controllerAs: 'main'
  });
}
angular
  .module('app')
  .config(config);
//...
</script>

Чтобы избежать использования $parent, когда нужно получить доступ к какому-либо из родительских контроллеров, при таком подходе просто пишем значение controllerAs требуемого контроллера, в нашем случае main. Понятно, что благодаря такому способу, мы избегаем также вызовов вроде $parent.$parent.

this в controllerAs


Синтаксис controllerAs подразумевает использование ключевого слова this в коде контроллера вместо $scope. При использовании controllerAs, контроллер по факту привязан к $scope, что, собственно, и даёт такую степень разделения.

Плохо:

function MainCtrl ($scope) {
  $scope.someObject = {};
  $scope.doSomething = function () {
  
  };
}
angular
  .module('app')
  .controller('MainCtrl', MainCtrl);

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

Плохо и хорошо:

Хорошо для наследования, но плохо для общего использования.

function MainCtrl ($scope) {
  this.someObject = {};
  this._$scope = $scope;
}
MainCtrl.prototype.doSomething = function () {
  // use this._$scope
};
angular
  .module('app')
  .controller('MainCtrl', MainCtrl);

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

Хорошо:

function MainCtrl () {
  this.someObject = {};
  this.doSomething = function () {
  
  };
}
angular
  .module('app')
  .controller('MainCtrl', MainCtrl);

Выше просто показан пример использования объектов и функций в контроллере. Конечно же, это совсем не значит, что мы собираемся использовать там логику…

Избегайте использование логики в контроллерах


Делегируйте логику фабрикам и сервисам.

Плохо:

function MainCtrl () {
  this.doSomething = function () {

  };
}
angular
  .module('app')
  .controller('MainCtrl', MainCtrl);

Хорошо:

function MainCtrl (SomeService) {
  this.doSomething = SomeService.doSomething;
}
angular
  .module('app')
  .controller('MainCtrl', MainCtrl);

Этот подход максимизирует повторное использование кода, надёжно инкапсулирует его функциональность и делает тестирование более лёгким и точным.

Сервисы


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

Хорошо:

function SomeService () {
  this.someMethod = function () {

  };
}
angular
  .module('app')
  .service('SomeService', SomeService);

Фабрики


Фабрики дают нам синглтон-модуль, для создания сервисных методов (таких, например, как связь приложения с сервером посредством REST). Создание и возврат по запросу цельного объекта поддерживает существующие привязки (binds) в контроллере обновлёнными, а ещё помогают избежать проблем с привязкой примитивов.

Важно: На самом деле «Фабрика» – это шаблон/реализация и не должно отождествляться с провайдером. Правильнее будет называть и фабрики, и сервисы «сервисами».

Плохо:

function AnotherService () {

  var someValue = '';

  var someMethod = function () {

  };
  
  return {
    someValue: someValue,
    someMethod: someMethod
  };

}
angular
  .module('app')
  .factory('AnotherService', AnotherService);

Хорошо:

Сначала мы создаём одноимённый объект внутри функции, а затем наполняем его методами. Это способствует как ручному документированию кода, так и генерации документации автоматическими средствами.

function AnotherService () {

  var AnotherService = {};
  
  AnotherService.someValue = '';

  AnotherService.someMethod = function () {

  };
  
  return AnotherService;
}
angular
  .module('app')
  .factory('AnotherService', AnotherService);

Здесь все привязки к примитивам остаются обновлёнными, а ещё это делает внутремодульную организацию пространства имён чуть более простой для понимания – здесь сразу видны приватные методы и переменные.

Продолжение следует...


В следующей части перевода:
  • Директивы
  • Promise в роутере, defer в контроллере
  • Избегайте $scope.$watch
  • Структура проекта
  • Соглашение об именовании и конфликты
  • Минификация и аннотация

Продолжение »

Данный стайлгайд находится в процессе доработки. Всегда актуальные рекомендации Вы найдёте на Github.
Tags:
Hubs:
+34
Comments 22
Comments Comments 22

Articles