Пользователь
0,0
рейтинг
6 сентября 2014 в 18:26

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

После прочтения 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.
Перевод: Todd Motto
@uoziod
карма
25,7
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (22)

  • +10
    • 0
      Действительно, они буквально на грани! Спасибо за ссылку!
  • +5
    Нужно больше конкретики, а то местами не совсем ясно, почему «так хорошо, а так плохо».
  • +1
    «MainCtrl» Насколько я знаю, в стайлгайдах (читал много где, а так же в ng-book) настойчиво рекомендуют писать не Ctrl, а Controller, обосновывая тем, что избегание сокращений является хорошим тоном при программировании.

    «Если вы используете prototype для изоляции от других контроллеров – это хорошо.» Хотелось бы подробнее, что значит изоляция от других контроллеров?

    Пример про IIFE, как-то адово описан, может стоило сначала указать на то, что следует разделять компоненты модуля на отдельные файлы, а потом показывать как использовать данную технику.

    По идее реализации функций нужно писать как можно ниже, дабы код был читаемым.

    Как-то странно вообще использовать $parent в шаблоне.

    Пример про фабрики странный какой-то, вполне правильно использовать и var someValue = ''; особенно если нужно реализовать приватные свойства\методы или например шорткаты.
  • 0
    Уже много раз встречал разногласия по поводу $scope и this. Так и не понял что лучше. В исходниках angular-ui
    • +1
      github.com/johnpapa/angularjs-styleguide#controllers вот тут прям отлично описано.
      • 0
        это то что я искал, спасибо, только:
        Добротный ответ на ваш вопрос — stackoverflow.com/a/14168699/1104483

        это несколько разнится с вашим ответом. Опять же. Кому верить?) я склонен считать что пора использовать this. Даже в курсе от CodeSchool, который проспонсировали Google, испольуется this вместо $scope.
  • +2
    В исходниках angular-ui пишут var self = this = $scope;
    Кому верить.

    Извиняюсь за два сообщения, писал с мобильного.
    • +3
      Добротный ответ на ваш вопрос — stackoverflow.com/a/14168699/1104483
      • 0
        вот опять же, написано что автор старается избегать this из за возможности создания изолированных $scope, но это относится к директивам больше чем к контроллерами, более того из за constrollerAs синтаксиса мы получаем символ аксессора к свойствам контроллера, а в случае с $scope нужно создавать отдельный vm ($socpe.vm = {}). Про проблемы с директивами я в принципе слышал, но на том же CodeSchool нет проблем с изолированным $scope при использовании this и controllerAs.

        Выше ответили — github.com/johnpapa/angularjs-styleguide#controllers
        Я склонен согласиться с этим стайл гайдом.
    • 0
      Думаю раньше this в контроллерах не было, был только $scope, сейчас есть this.
      • 0
        Скорее всего, Вы правы. Пора переучиваться на this. Да и код получится проще.
  • 0
    Я вот момент не понял
    controllerAs-синтаксис… делает скоупы вложенными
    вложенными относительно чего?
    • +4
      Ну смотрите. пусть есть 3 контроллера с вложенностью

      <div ng-controller="ParentCtrl as p">
        <div ng-controller="ChildFirstCtrl as cf">
          <div ng-controller="ChildSecondCtrl as cs">
            Пример использования данных из родительского контроллера в дочернем {{ p.a + cf.b * cs.c }}
          </div>
        </div>
      </div>
      


      У нас есть возможность использовать данные из родительских контроллеров в дочернем.
      • 0
        Спасибо, предельно ясно. :)
        • 0
          Если вам интересно, вот мой проекта ради веселья в котором есть этот подход, только в angular ui router github.com/xgrommx/angular-vk-app
          • 0
            Интересно! :) Но с первых строк колдунство с es6, боже мой, как я отстал от жизни!
            • 0
              Доганяйте =)
  • –1
    Есть очень приятная штука github.com/CaryLandholt/ng-classify
    Позволяет писать вот такие штуки. Это лучшее, что я нашел для ангуляра
    class Home extends Controller
        constructor: (userService) ->
            @ save = (username) ->
                userService.addUser username
    
  • 0
    Модули в AngularJS могут быть объявлены различными способами: либо с использованием переменной, либо через getter-синтаксис. Всегда используйте второй способ (более того, он рекомендован разработчиками фреймворка).

    А можно конкретно ткнуть в место по указанной ссылке, где разработчики такое рекомендуют?
  • 0
    Спасибо, начал следовать вашему стайлгайду. Поставил Batarang, он начал выдавать Hint'ы якобы:

    1. app не подходит как название для приложения, лучше используйте CamelCase, типа MyApp.
    2. CartCtrl не красиво, лучше используйте CartController

    Дабы не доставали эти хинты, пришлось последовать его совету.

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