how to handle all this angular 1.x

Мы разрабатываем на angular 1.5. На этом месте обычно все морщатся, но надо признать, что мы такие не одни, а, значит, найдутся потребители совета, который проследует далее. А если вы пишете на реакте,

можете просто подивиться тому, как плохо продуманы модули и dependency injection в первом ангуляре, и как мы хитро пытаемся с этим жить.

Напомню, как по идее объявляются модули на примере крайне эгоистичного кота без внешнего API

кот-ангуляр с http://whichcatisyourjavascriptframework.com/
angular.module(`catModule`,[’humanModule’, ‘plateModule’])
.controller(‘CatController’, function(PlateService, HumanService){})
angular.module(`humanModule`,[‘plateModule’])
.controller(‘HumanController’, function(PlateService) {})
.service(‘HumanService’, function(){});
angular.module(`plateModule`,[])
.directive(‘PlateDirective’, function(PlateService) {})
.service(‘PlateService’, function(){});

Ангуляр предполагает, что работает с одним большим файлом, где все объявлено таким образом. Конечно, так никто никогда не разрабатывает, а хранит каждый компонент в отдельном файле. Самый простой способ собрать их вместе — конкатенировать разрозненные файлы при помощи grunt или gulp. В таком случае получается следующая структура:

// src/cat/module.js
angular.module(`catModule`, [’humanModule’, ‘plateModule’]);
// scr/cat/catController.js
angular.module(`catModule`)
.controller(‘CatController’, function(PlateService, HumanService){})

Мы аккуратно склеиваем все *.js файлы, дополнительно заботясь о том, чтобы модуль объявлялся(объявление модуля от ссылки на него отличается исключительно наличием второго аргумента типа “массив строк”. Зачем это вообще?) до того, как к нему начинают добавляться контроллеры и сервисы.

Этот способ самый простой, но

  • не дает никакой возможности реализовать независимую модульную структуру, подключая в проект лишь необходимые модули
  • навигироваться по проекту неудобно(никакого тебе ctrl+click)
  • если нейминг нестрогий, и в нашем humanModule появится какой-нибудь washCatService, впоследствие будет трудно понять, к какому модулю этот сервис относится, придется искать по проекту

Как сделать лучше?

Можно подключить requirejs, browserify или webpack(я делала на webpack) и прийти к такому варианту

// src/cat/module.js
module.exports = 'catModule';
angular.module('catModule',[
require('src/human'),
require('src/plate')
]);
require('./catController');
// scr/cat/catController.js
module.exports = angular.module(`catModule`)
.controller(‘CatController’, function(PlateService, HumanService){})

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

module.exports = angular.module('catModule',[
require('src/human'),
require('src/plate')
]).name;
require('./catController');

Мы писали в таком стиле довольно долго, пока не придумали великолепнейшее решение(с использованием es6 spread-syntax)

// src/cat/module.js
module.exports = angular.module('catModule',[
require('src/human'),
require('src/plate')
])
.controller(...require('./catController')
.name;
// scr/cat/catController.js
module.exports = [
‘CatController’,
/* @ngInject */function(PlateService, HumanService){})
];

/*@ngInject*/ тут не зря, ведь мы отошли из стандартной ангуляровской записи, а, значит, минификатор, который рано или поздно появится в проекте, не догадается, что нам необходим dependency injection, и ничего работать не будет. Тут об этом подробнее

Отказавшись от простой конкатенации в пользу такой записи, мы

  • сделали возможной разработку независимых модулей, оставив себе возможность хранить в проекте нечто, успешно не попадающее в бандл без магии в настройках сборщика. Удобно для разработки переиспользуемых библиотек.
  • улучшили навигацию по проекту, перейдя от ctrl+f к ctrl+click
  • сэкономили много бесполезных упоминаний имени модуля, которые постоянно надо тупо копировать при создании новых компонентов, а потом, если что, мучительно переименовывать

и

  • получили потенциальное место для багов — если забыть @ngInject, отлаживаться потом очень неудобно. А линтеров на него пока не придумали
  • мы все еще можем не указать при объявлении модуля список всех необходимых зависимостей, но получить работающее приложение, если они указаны в другом модуле(а, значит, попали в бандл).

В следующий раз расскажу как мы используем es6-классы c angular. И как перешли от унылого способа к хорошему.