Angular Style Guide

Одобрена Angular Team

Agenda

  • Single Responsibility (Единственная обязанность)
  • IIFE
  • Modules (Модули)
  • Controllers (Контроллеры)
  • Services (Сервисы)
  • Factories (Фабрики)
  • Data Services (Сервисы данных)
  • Directives (Директивы)
  • Resolving Promises for a Controller (Работа с Объектами Promise в Контроллерe)
  • Manual Annotating for Dependency Injection (Аннотация для Внедренной Зависимости)
  • Minification and Annotation (Минификация и аннотация)
  • Exception Handling (Обработка Исключений)
  • Naming (Именования)
  • Modularity (Модульность)
  • Startup Logic (Логика Запуска Приложения)
  • Angular $ Wrapper Services (Angular и Интерфейсные Сервисы)
  • Testing (Тестирование)
  • Animations (Анимации)
  • Comments (Комментарии)
  • Constants (Константы)
  • Routing (Маршрутизация)
  • Useful (Полезное)

John Papa

Эксперт разработчик Googleрегиональный директор 

Microsoft и MVP, автор более 100 статей и 10 книг, а также бывший евангелист технологии для клиент команд Microsoft.

Single Responsibility

Единственная обязанность

Правило №1

Определяйте 1 компонент в одном файле.

В одном и том же файле определяется модуль(module) app вместе с его зависимостями, определяется контроллер(controller), а также сервис(factory).

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

function SomeController() { }

function someFactory() { }

Каждый компонент разнесен в свой отдельный файл.

/* рекомендовано */

// app.module.js
angular
    .module('app', ['ngRoute']);
/* рекомендовано */

// someController.js
angular
    .module('app')
    .controller('SomeController', SomeController);

function SomeController() { }
/* рекомендовано */

// someFactory.js
angular
    .module('app')
    .factory('someFactory', someFactory);

function someFactory() { }

IIFE

Immediately-Invoked Function Expression

Немедленно Исполняемые Функции

Оборачивайте компоненты Angular в Немедленно Исполняемые Функции(IIFE - Immediately Invoked Function Expression).

(function() {
    'use strict';
    ...

})();

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

Когда ваш код будет сжат и упакован (bundled and minified) в один файл для размещения его на рабочем сервере, то коллизий станет намного больше, чем их было до минификации. IIFE защитит ваш код, обеспечивая область видимости переменных только в немедленно исполняемых функциях(IIFE), которые оборачивают ваш код.

Зачем?:

Зачем?:

// logger.js
angular
    .module('app')
    .factory('logger', logger);

// функция logger добавлена как глобальная переменная
function logger() { }
// storage.js
angular
    .module('app')
    .factory('storage', storage);

// функция storage добавлена как глобальная переменная
function storage() { }
// logger.js
(function() {
    'use strict';

    angular
        .module('app')
        .factory('logger', logger);

    function logger() { }
})();
// storage.js
(function() {
    'use strict';

    angular
        .module('app')
        .factory('storage', storage);

    function storage() { }
})();

Больше нет глобальных переменных

Modules

Модули

Избегайте коллизий имен

Используйте конвенцию уникальных имен с разделителями для подмодулей

Почему?

Уникальные имена помогают избежать коллизий в именах модулей. Разделители определяет сам модуль и его подмодульную иерархию. Например, app может быть вашим корневым модулем, а модули app.dashboard и app.users могут использоваться как имена модулей, зависимые от app.

Сеттеры (Setters)

Объявляйте модули без переменных, используйте сеттеры (setters).

Так как обычно в файле 1 компонент, поэтому вряд ли потребуется вводить переменную для модуля.

/* избегайте этого */
var app = angular.module('app', [
    'ngAnimate',
    'ngRoute',
    'app.shared',
    'app.dashboard'
]);
/* рекомендовано */
angular
    .module('app', [
        'ngAnimate',
        'ngRoute',
        'app.shared',
        'app.dashboard'
    ]);

Используйте простой синтаксис сеттера.

Почему?

Геттеры (Getters)

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

Так вы производите более читаемый код, а также избегаете утечек и коллизий переменных.

/* избегайте этого */
var app = angular.module('app');
app.controller('SomeController', SomeController);

function SomeController() { }
/* рекомендовано */
angular
    .module('app')
    .controller('SomeController', SomeController);

function SomeController() { }

Почему?

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

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

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

- Используйте `angular.module('app', []);` для определения модуля.
- Используйте `angular.module('app');` чтобы получить модуль.

Почему?

Именованные или Анонимные Функции

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

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

Почему?

/* избегайте этого */
angular
    .module('app')
    .controller('Dashboard', function() { })
    .factory('logger', function() { });
// dashboard.js
angular
    .module('app')
    .controller('Dashboard', Dashboard);

function Dashboard() { }
// logger.js
angular
    .module('app')
    .factory('logger', logger);

function logger() { }

Контроллеры

Controllers

Синтаксис controllerAs в представлении

Используйте синтаксис controllerAs, который работает поверх синтаксиса классический контроллер со $scope

Контроллер создается, как JavaScript-объект с ключевым словом "new" и затем предоставляется этот единственный экземпляр объекта. То есть синтаксис controllerAs намного ближе и похоже на конструктор языка JavaScript, чем классический синтаксис $scope.

Почему?:

Почему?:

Помогает избежать использование вызовов $parent в представлениях с вложенными контроллерами.

<!-- избегайте этого -->
<div ng-controller="Customer">
    {{ name }}
</div>
<!-- рекомендовано -->
<div ng-controller="Customer as customer">
    {{ customer.name }}
</div>

Это позволяет использовать в представлении связывание на свойство объекта "через точку" (например вместо name будет customer.name), что является более концептуальным, проще для чтения, и помогает избегать многих ссылочных проблем, которые могут возникнуть без использования "точки".

Почему?:

Синтаксис controllerAs в контроллере

Используйте синтаксис controllerAs поверх синтаксиса классический контроллер со $scope.

Синтаксис controllerAs использует внутри контроллеров ключевую переменную this, которая привязывается к $scope.

controllerAs - это только синтаксический сахар поверх $scope. Вы все равно можете использовать связывание в представлениях и все равно имеете доступ к методам $scope.

Почему?:

Избавляет от искушения использования методов $scope внутри контроллера, когда это не требуется явно, и это позволяет перенести методы в фабрику(factory). Предпочтительнее использовать $scope в фабрике, а в контроллере только если это действительно необходимо. Например, когда мы подписываемся и рассылаем события с помощью $emit$broadcast, or $on перенесите эти сервисы в фабрику, и вызывайте методы фабрики в контроллере.

/* избегайте этого */
function Customer($scope) {
    $scope.name = {};
    $scope.sendMessage = function() { };
}
/* рекомендовано - 
но изучите следующую секцию */
function Customer() {
    this.name = {};
    this.sendMessage = function() { };
}

Почему?:

Синтаксис controllerAs с переменной vm

Сохраните this в переменную, когда используете синтаксис controllerAs. Выберите постоянное имя для переменной, такое как vm, что будет значить ViewModel.

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

/* избегайте этого */
function Customer() {
    this.name = {};
    this.sendMessage = function() { };
}
/* рекомендовано */
function Customer() {
    var vm = this;
    vm.name = {};
    vm.sendMessage = function() { };
}

Почему?:

Замечание:

Вы можете избежать любые jshint предупреждения, если разместите над строкой кода комментарий, как в приведенном ниже примере. Это не требуется, если функция названа с помощью ВерхнегоРегистра(UpperCasing), так как согласно этой конвенциии, это означает, что функция является конструктором контроллера Angular.

/* jshint validthis: true */
var vm = this;

Замечание:

Когда создаете наблюдающие функции(watcher) в контроллерах типа controller as, вы можете наблюдать за переменной следующего вида vm.*. (Создавайте наблюдающие функции с предупреждением, что они создают дополнительную нагрузку на цикл digest.)

<input ng-model="vm.title"/>
function SomeController($scope, $log) {
    var vm = this;
    vm.title = 'Some Title';

    $scope.$watch('vm.title', function(current, original) {
        $log.info('vm.title was %s', original);
        $log.info('vm.title is now %s', current);
    });
}

Привязываемые Члены Сверху

Помещайте привязываемые члены в верхней части контроллера, в алфавитном порядке, и не раскидывайте их в коде контроллера где попало.

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

Почему?:

Написание анонимных функций по месту использования может конечно и проще, но когда такие функции содержат много строк кода, то это значительно снижает читабельность кода. Определяйте функции ниже привязываемых членов, тем самым вы перенесете детали реализации вниз отдельно. Функции определяйте как hoisted, то есть они будут подняты наверх области видимости. А привязываемые члены наверху повысят читабельность кода.

Почему?:

/* избегайте этого */
function Sessions() {
    var vm = this;

    vm.gotoSession = function() {
      /* ... */
    };
    vm.refresh = function() {
      /* ... */
    };
    vm.search = function() {
      /* ... */
    };
    vm.sessions = [];
    vm.title = 'Sessions';
}
/* рекомендовано */
function Sessions() {
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = refresh;
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';

    ////////////

    function gotoSession() {
      /* */
    }

    function refresh() {
      /* */
    }

    function search() {
      /* */
    }
}
/* избегайте этого */
function Sessions(data) {
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = function() {
        /**
         * эти
         * строки
         * кода
         * ухудшают
         * читабельность
         */
    };
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';
}
/* рекомендовано */
function Sessions(dataservice) {
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = dataservice.refresh; // одна строка
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';
}

Замечание:

Если функция однострочная, то разместите и ее наверху, но так чтобы это не усложняло читабельность.

Определения Функций Для Скрытия Деталей Реализации

Используйте определения функций для скрытия деталей реализации. Держите свои привязываемые члены наверху. Если нужно в контроллере сделать функцию привязываемой, укажите это в группе привязываемых членов и ссылайтесь на данную функцию, которая реализована ниже. Это подробно описано в секции Привязываемые Члены Сверху

Размещение деталей реализации функции внизу скрывает эту сложность ниже и таким образом все важные вещи находятся на видном месте сверху.

Функции определены как hoisted (определены в самом верху области видимости), поэтому не надо заботиться об их использовании перед объявлением, так как это было бы с объявлениями выражений функций (function expressions).

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

Почему?:

Почему?:

Почему?:

Почему?:

Почему?:

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

С выражениями функций(function expressions) порядок будет критичен.

/**
* избегайте этого
* Использование выражений функций (function expressions).
*/
function Avengers(dataservice, logger) {
    var vm = this;
    vm.avengers = [];
    vm.title = 'Avengers';

    var activate = function() {
        return getAvengers().then(function() {
            logger.info('Activated Avengers View');
        });
    }

    var getAvengers = function() {
        return dataservice.getAvengers().then(function(data) {
            vm.avengers = data;
            return vm.avengers;
        });
    }

    vm.getAvengers = getAvengers;

    activate();
}
/*
* рекомендовано
* Объявления функций и
* привязанные члены смещены вверх.
*/
function Avengers(dataservice, logger) {
    var vm = this;
    vm.avengers = [];
    vm.getAvengers = getAvengers;
    vm.title = 'Avengers';

    activate();

    function activate() {
        return getAvengers().then(function() {
            logger.info('Activated Avengers View');
        });
    }

    function getAvengers() {
        return dataservice.getAvengers().then(function(data) {
            vm.avengers = data;
            return vm.avengers;
        });
    }
}

Логика Контроллера Отдельно

Переносите логику контроллера в сервисы и фабрики.

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

Вся логика в сервисе может быть легко изолирована в модульном тесте, а вызовы этой логики в контроллере могут быть фиктивно реализованы (mocked).

Из контроллера удаляются зависимости и скрываются подробности реализации.

Почему?:

Почему?:

Почему?:

/* избегайте этого */
function Order($http, $q, config, userInfo) {
    var vm = this;
    vm.checkCredit = checkCredit;
    vm.isCreditOk;
    vm.total = 0;

    function checkCredit() {
        var settings = {};
        // Get the credit service base URL from config
        // Set credit service required headers
        // Prepare URL query string or data object with request data
        // Add user-identifying info so service gets the right credit limit for this user.
        // Use JSONP for this browser if it doesn't support CORS
        return $http.get(settings)
            .then(function(data) {
             // Unpack JSON data in the response object
               // to find maxRemainingAmount
               vm.isCreditOk = vm.total <= maxRemainingAmount
            })
            .catch(function(error) {
               // Interpret error
               // Cope w/ timeout? retry? try alternate service?
               // Re-reject with appropriate error for a user to see
            });
    };
}
/* рекомендовано */
function Order(creditService) {
    var vm = this;
    vm.checkCredit = checkCredit;
    vm.isCreditOk;
    vm.total = 0;

    function checkCredit() {
       return creditService.isOrderTotalOk(vm.total)
    .then(function(isOk) { vm.isCreditOk = isOk; })
          .catch(showServiceError);
    };
}

Один Контроллер - Одно Представление

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

Использование контроллера с несколькими представлениями хрупко и ненадежно. А для больших приложений требуется хорошее покрытие тестами end to end (e2e) для проверки стабильности.

Почему?:

Получение Контроллеров

Когда контроллер и его представление уже создано, и если нужно что-то повторно использовать (контроллер и представление), определяйте экземпляр контроллера вместе с его маршрутом (route).

Замечание: 

Если представление загружается не через маршрут, тогда используйте синтаксис ng-controller="Avengers as vm".

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

Почему?:

/* избегайте этого - когда используется маршрут
и необходимо динамическое назначение 
контроллера и представления */

// route-config.js
angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
          templateUrl: 'avengers.html'
        });
}
<!-- avengers.html -->
<div ng-controller="Avengers as vm">
</div>
/* рекомендовано */

// route-config.js
angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
            templateUrl: 'avengers.html',
            controller: 'Avengers',
            controllerAs: 'vm'
        });
}
<!-- avengers.html -->
<div>
</div>

Сервисы (Services)

Синглтоны

Сервисы создаются с помощью ключевого слова new. Используйте this для публичных методов и переменных. Так как они очень похожи на фабрики, то используйте фабрики для согласованности.

Замечание:

Все Angular сервисы являются синглтонами. Это значит, что создается только один экземпляр сервиса на один инжектор.

// service
angular
    .module('app')
    .service('logger', logger);

function logger() {
  this.logError = function(msg) {
    /* */
  };
}
// factory
angular
    .module('app')
    .factory('logger', logger);

function logger() {
    return {
        logError: function(msg) {
          /* */
        }
   };
}

Фабрики (Factories)

Единственная Обязанность (Single Responsibility)

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

Синглтон

Фабрики это синглтоны, которые возвращают объект, содержащий свойства и методы сервиса.

Замечание:

Все Angular сервисы являются синглтонами.

Доступные Члены Наверх

Помещайте вызываемые члены сервиса (интерфейс) наверху, используя технику Revealing Module Pattern.

Размещение вызываемых членов в верхней части улучшает читабельность и дает вам возможность быстро определить, какие члены сервиса могут быть вызваны и должны быть модульно оттестированы (либо фиктивно имплементированы - mocked).

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

Почему?:

Почему?:

Размещение функций в обычном прямом порядке может быть и проще, но когда эти функции становятся многострочными, это снижает читабельность и вынуждает много скроллить. Определяя вызываемый интерфейс(в виде возвращаемого сервиса), вы убираете детали реализации вниз. А размещенный сверху интерфейс улучшает читабельность.

Почему?:

/* избегайте этого */
function dataService() {
  var someValue = '';
  function save() {
    /* */
  };
  function validate() {
    /* */
  };

  return {
      save: save,
      someValue: someValue,
      validate: validate
  };
}
/* рекомендовано */
function dataService() {
    var someValue = '';
    var service = {
        save: save,
        someValue: someValue,
        validate: validate
    };
    return service;

    ////////////

    function save() {
        /* */
    };

    function validate() {
        /* */
    };
}

Сервисы данных

(Data Services)

Отделите вызовы данных

Делайте рефакторинг логики работы с данными и взаимодействия этих данных с фабрикой. Создавайте сервисы данных, ответственных за вызовы XHR, локальное хранилище(local storage), работу с памятью или за любые другие операции с данными.

Ответственность контроллера - это предоставление или сбор информации для представления. Он не должен заботиться о том, как работать с данными, он только должен знать кого попросить об этом. В сервисы для данных переносится вся логика работы с данными. Это делает контроллер более простым и более сфокусированным для работы с представлением.

Почему?:

Это позволяет более проще тестировать (фиктивно или реально) вызовы данных в контроллере, который использует сервис для данных.

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

Сервис данных вызывается потребителем (контроллером), и скрывает реализацию от потребителя. 

Почему?:

Почему?:

Замечание:

/* рекомендовано */

// фабрика сервиса данных
angular
    .module('app.core')
    .factory('dataservice', dataservice);

dataservice.$inject = ['$http', 'logger'];

function dataservice($http, logger) {
    return {
        getAvengers: getAvengers
    };

    function getAvengers() {
        return $http.get('/api/maa')
            .then(getAvengersComplete)
            .catch(getAvengersFailed);

        function getAvengersComplete(response) {
            return response.data.results;
        }

        function getAvengersFailed(error) {
            logger.error('XHR Failed for getAvengers.' + error.data);
        }
    }
}
/* рекомендовано */

// контроллер вызывает фабрику сервиса данных
angular
    .module('app.avengers')
    .controller('Avengers', Avengers);

Avengers.$inject = ['dataservice', 'logger'];

function Avengers(dataservice, logger) {
    var vm = this;
    vm.avengers = [];

    activate();

    function activate() {
        return getAvengers().then(function() {
            logger.info('Activated Avengers View');
        });
    }

    function getAvengers() {
        return dataservice.getAvengers()
            .then(function(data) {
                vm.avengers = data;
                return vm.avengers;
            });
    }
}

Возвращение Объекта Promise для Контроллера

Если сервис данных возвращает promise типа $http, то в вызывающей функции возвращайте promise тоже.

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

Почему?:

/* рекомендовано */

activate();

function activate() {
    /**
     * Шаг 1
     * Запрашиваем функцию getAvengers function
     * для получения данных и ждем promise
     */
    return getAvengers().then(function() {
        /**
         * Шаг 4
         * Выполняем действие принятия финального объекта promise
         */
        logger.info('Activated Avengers View');
    });
}

function getAvengers() {
      /**
       * Шаг 2
       * Запрашиваем сервис для данных
       * и ждем promise
       */
      return dataservice.getAvengers()
          .then(function(data) {
              /**
               * Шаг 3
               * инициализируем данные и принимаем promise
               */
              vm.avengers = data;
              return vm.avengers;
      });
}

Директивы (Directives)

Одна Директива - Один Файл

Создавайте только одну директиву в файле. Называйте файл именем директивы.

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

Одну директиву в файле легче поддерживать.

Почему?:

Почему?:

/* избегайте этого */
/* directives.js */

angular
    .module('app.widgets')

    /* директива для заказа, которая специфична для модуля заказов */
    .directive('orderCalendarRange', orderCalendarRange)

    /* директива продажи, которая может быть использована везде в модуле продаж */
    .directive('salesCustomerInfo', salesCustomerInfo)

    /* директива крутилки (spinner), которая может быть использована во всех модулях */
    .directive('sharedSpinner', sharedSpinner);

function orderCalendarRange() {
    /* детали реализации */
}

function salesCustomerInfo() {
    /* детали реализации */
}

function sharedSpinner() {
    /* детали реализации */
}
/* рекомендовано */
/* calendarRange.directive.js */

/**
* @desc директива заказа, которая специфична модулю заказов в компании Acme
* @example <div acme-order-calendar-range></div>
*/
angular
    .module('sales.order')
    .directive('acmeOrderCalendarRange', orderCalendarRange);

function orderCalendarRange() {
    /* детали реализации */
}
/* рекомендовано */
/* customerInfo.directive.js */

/**
* @desc директива продажи, которая может быть использована везде в модуле продаж компании Acme
* @example <div acme-sales-customer-info></div>
*/
angular
    .module('sales.widgets')
    .directive('acmeSalesCustomerInfo', salesCustomerInfo);

function salesCustomerInfo() {
    /* implementation details */
}
/* рекомендовано */
/* spinner.directive.js */

/**
* @desc директива крутилки (spinner), которая может быть использована во всех модулях компании Acme
* @example <div acme-shared-spinner></div>
*/
angular
    .module('shared.widgets')
    .directive('acmeSharedSpinner', sharedSpinner);

function sharedSpinner() {
    /* детали реализации */
}

Манипулирование Элементами DOM в Директиве

Используйте директивы, если нужно манипулировать элементами DOM напрямую. Но если существуют альтернативные способы, то используйте лучше их. Например, для изменения стилей используйте CSS, для эффектов сервисы анимации, для управления видимостью используйте ngShow и ngHide.

Добавляйте Директивам Уникальный Префикс

Добавляйте директивам короткий, уникальный, пояснительный префикс, такой как 

acmeSalesCustomerInfo, директива будет объявлена в HTML как acme-sales-customer-info

Уникальный короткий префикс говорит о контексте и происхождении директивы. Например, префиксcc- может рассказать нам, что директива является частью приложения CodeCamper, а acme- это директива для компании Acme.

Почему?:

Замечание:

Не используйте префикс ng- для своих директив, так как он зарезервирован для директив AngularJS. Также исследуйте префиксы широко используемых директив для избежания конфликтов имен, например, префикс ion- используется для директив Ionic Framework.

Ограничивайте Элементы и Атрибуты

При создании директивы, которая планируется как самостоятельный элемент, применяйте ограничение E(разработано, как элемент) или по необходимости ограничение A (разработано, как атрибут). В основном, если директива разрабатывается как элемент, ограничения E вполне достаточно. Хотя Angular позволяет использовать EA, но все же лучше определится как реализовывать директиву, либо как самостоятельный отдельный элемент, либо как атрибут для улучшения функциональности существующего DOM-элемента.

Почему?:

Это имееет смысл.

Почему?:

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

Замечание:

EA используется по умолчанию для Angular 1.3 +

<!-- избегайте этого -->
<div class="my-calendar-range"></div>
/* избегайте этого */
angular
    .module('app.widgets')
    .directive('myCalendarRange', myCalendarRange);

function myCalendarRange() {
    var directive = {
        link: link,
        templateUrl: '/template/is/located/here.html',
        restrict: 'C'
    };
    return directive;

    function link(scope, element, attrs) {
      /* */
    }
}
<!-- рекомендовано -->
<my-calendar-range></my-calendar-range>
<div my-calendar-range></div>
/* рекомендовано */
angular
    .module('app.widgets')
    .directive('myCalendarRange', myCalendarRange);

function myCalendarRange() {
    var directive = {
        link: link,
        templateUrl: '/template/is/located/here.html',
        restrict: 'EA'
    };
    return directive;

    function link(scope, element, attrs) {
      /* */
    }
}

Директивы и ControllerAs

Используйте синтактисcontroller as в директиве, чтобы директива была согласована с использованием синтаксиса controller as в паре контроллера и представлении.

Это имет смысл и не так сложно.

Почему?:

angular
    .module('app')
    .directive('myExample', myExample);

function myExample() {
    var directive = {
        restrict: 'EA',
        templateUrl: 'app/feature/example.directive.html',
        scope: {
            max: '='
        },
        link: linkFunc,
        controller: ExampleController,
        controllerAs: 'vm'
    };

    return directive;

    function linkFunc(scope, el, attr, ctrl) {
        ...
    }
}

Resolving Promises for a Controller

(Работа с Объектами Promise в Контроллерe)

Активация объектов promise в контроллере

Размещайте стартовую начальную логику для контроллера в функции activate.

Почему?:

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

Почему?:

Функция activate делает удобным повторное использование логики для обновления контроллера/представления, держит всю логику вместе, делает более быстрой работу пользователя с представлением, делает анимацию более простой с директивами ng-view и ui-view, ну и делает ориентирование разработчика в коде более энергичным и быстрым.

/* избегайте этого */
function Avengers(dataservice) {
    var vm = this;
    vm.avengers = [];
    vm.title = 'Avengers';

    dataservice.getAvengers().then(function(data) {
        vm.avengers = data;
        return vm.avengers;
    });
}
/* рекомендовано */
function Avengers(dataservice) {
    var vm = this;
    vm.avengers = [];
    vm.title = 'Avengers';

    activate();

    ////////////

    function activate() {
        return dataservice.getAvengers().then(function(data) {
            vm.avengers = data;
            return vm.avengers;
        });
    }
}

Manual Annotating for Dependency Injection (Аннотация для Внедренной Зависимости)

Minification and Annotation

(Минификация и аннотация)

Exception Handling (Обработка Исключений)

Naming (Именования)

Modularity (Модульность)

Startup Logic

(Логика Запуска Приложения)

Angular $ Wrapper Services (Angular и Интерфейсные Сервисы)

Testing (Тестирование)

Animations (Анимации)

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

JSHint & JSCS

Constants (Константы)

Routing (Маршрутизация)

Task Automation (Автоматизация)

Angular Style Guide

By Shuhratbek Mamadaliyev

Angular Style Guide

  • 1,420